我的算法不可能这么简单—珂朵莉树
文章目录
- 进入正题
- 珂朵莉树的起源
- 题目简述
- 题目分析
- 珂朵莉树的实现
- 萌新三连
- 1.明明查询的右端点是12,但是要split(13)呢?
- 2.为什么要先分裂右端点,然后再分裂左端点呢?
- 3.获取到区间之后呢?
- 其余操作实现
- 例题总代码
- 小结
- 其他例题
- [SCOI2010]序列操作
- CF817F MEX Queries
- 完结撒花
参考文章 : https://www.cnblogs.com/WAMonster/p/10181214.html
进入正题
珂朵莉树的起源
珂朵莉树原名老司机树(Old Driver Tree,ODT),由2017年一场CF比赛中提出的数据结构,因为题目背景主角是《末日时在做什么?有没有空?可以来拯救吗?》的主角珂朵莉,因此该数据结构被称为珂朵莉树。
题目简述
(原题请转->CF896C Willem, Chtholly and Seniorious)
请你写一种奇怪的数据结构,支持:
- 1 l r x :将 [l,r] 区间所有数加上 x
- 2 l r x :将 [l,r] 区间所有数改成 x
- 3 l r x :输出将 [l,r] 区间从小到大排序后的第 x 个数是多少(即区间第 x 小,数字大小相同算多次,保证 1≤x≤r−l+11\leq x \leq r-l+11≤x≤r−l+1 )
- 4 l r x y :输出[l,r] 区间每个数字的 x次方的和 模 y 的值 (即(∑i=lraix)mody)(\sum^r_{i=l}a_i^x) \mod y )(∑i=lraix)mody)
【输入格式】 这道题目的输入格式比较特殊,需要选手通过seed 自己生成输入数据。 输入一行四个整数 n,m,seed,vmaxn,m,seed,v_{max}n,m,seed,vmax(1≤n,m≤105,0≤seed≤109+7,1≤vmax≤109)(1\leq n,m\leq 10^{5},0\leq seed \leq 10^{9}+7, 1\leq v_{max} \leq 10^{9})(1≤n,m≤105,0≤seed≤109+7,1≤vmax≤109) 其中 n 表示数列长度,m 表示操作次数,后面两个用于生成输入数据。 数据生成的伪代码如下:
def rnd(): ret = seedseed = (seed * 7 + 13) mod 1000000007return retfor i = 1 to n:a[i] = (rnd() mod vmax) + 1for i = 1 to m:op = (rnd() mod 4) + 1l = (rnd() mod n) + 1r = (rnd() mod n) + 1if (l > r): swap(l, r)if (op == 3):x = (rnd() mod (r - l + 1)) + 1else:x = (rnd() mod vmax) + 1if (op == 4):y = (rnd() mod vmax) + 1
其中上面的op指题面中提到的四个操作。
【输出格式】 对于每个操作3和4,输出一行仅一个数。
输入样例 #1:
10 10 7 9
输出 #1:
2
1
0
3输入样例 #2:
10 10 9 9
输出 #2:
1
1
3
3
题目分析
一说起区间维护问题,我们就能想到线段树,主席树,树状数组,Splay,分块,莫队等数据结构,但是读完题目我们发现,第四个操作涉及每个数字的相关操作,上面提到的结构只有莫队可以做到,但是复杂度太高,我们需要一个更加高效的数据结构 珂朵莉来维护这些操作。
珂朵莉树的实现
联想题目,发现有一个操作为令一片区间值相同,我们自然想到,如果能用一个点来表示这一个区间,那么自然会省很多力,诶,一个点怎么表示一个区间呢 ,于是,自然而然的想到了结构体!
珂朵莉树基于std::set
,现在我们先定义一段连续的值相同的区间:
using ll = long long;
struct node{int l,r;//l:区间左端点,r:区间右端点mutable ll val;//区间值bool operator<(const node &a)const {return l<a.l;}//重载<比较符,使区间在set中为从小到大的顺序排列node(int L,int R,ll Val):l(L),r(R),val(Val){}//构造函数(赋值专用)node(int L):l(L){}//构造函数(提取区间专用)
};
mutable
关键字定义一个强制可变量,这样可以使得我们在 set
中修改 val 的值!
由于珂朵莉树基于 std::set
因此接下来自然就是将 node
结点放入set
中:
set<node> s;
using si = set<node>::iterator;//因为set<node>::iterator实在是太长了,为了后续操作的方便,我们给它起个别名 si ,是不是感觉瞬间清爽
然而每次在查询操作时,我们不可能保证查询的区间端点正好在一个结构体结点中,因此需要查找到区间左右端点,获取到待查询或者待修改的区间,这便引出了下面的分裂函数:
si split(int pos){si it = s.lower_bound(node(pos));//获取第一个左端点不小于pos的结点的迭代器if(it != s.end() && it->l==pos) return it;//如果左端点已经是pos并且不是末尾,直接返回该迭代器--it;//否则pos一定在前一个结点区间内int l=it->l,r=it->r;//保存结点信息ll val = it->val;s.erase(it);//删除该结点s.insert(node(l,pos-1,val));//插入[l,pos-1]这个区间return s.insert(node(pos,r,val)).first;//插入[pos,r]区间,并返回指向该区间的迭代器
}
split
函数的作用就是查找set
中第一个左端点不小于pos
的结点,如果找到的结点的左端点等于pos
便直接返回指向该结点的迭代器,如果不是,说明pos
包含在前一个结点所表示的区间之间,此时便直接删除包含pos
的结点,然后以pos
为分界点,将此结点分裂成两份,分别插入set
中,并返回指向后一个分裂结点的迭代器。
有点抽象?下面以图示说明split
函数到底做了什么:
- 首先我们假设
set
中有三个node
结点,这三个结点所表示的区间长度为14 ,如下图:
- 不妨以提取区间 [10,12] 为例详细展开((躁动的读者)诶,说好的查询10到12呢,怎么下面扯了一堆13?别急,后续将会揭晓 ):
- 如果我们要查询序列第13个位置,首先执行
si it = s.lower_bound(node(13));
此时it
将成为一个指向第三个结点的迭代器,为什么是第三个结点,而不是第二个结点呢,因为lower_bound
这个函数获取的是第一个左端点l
不小于13
的结点,所以it
是指向第三个结点的。
然后执行判断语句,发现第三个结点的左端点不是13,不满足条件,说明13必包含在前一个结点中,继续向下执行,让it
指向前一个结点:
先将该结点的信息保存下来:int l = 10, r = 13, val = 2
然后直接删除该结点
以pos
为分界点,将被删除的结点分裂为 [l,pos-1] ,[pos,r]这两块,并返回指向[pos,r]这个区间的迭代器,事实上return s.insert(node(pos,r,val)).first;
,便做到了插入[pos,r]这端区间,并返回指向它的迭代器,有一个insert函数返回值为pair类型,其中pair的第一个元素就是元素插入位置的迭代器。
至此13位置已经分裂完成,然后是查询第10个位置,查询步骤同上,但是10号点满足if语句,便直接返回了。
- 由上述步骤,为了提取区间 [10,12],我们执行了两次
split
,一次为split(13)
,一次为split(10)
,并获得了两个迭代器,一个指向第二结点,一个指向第三结点。
萌新三连
1.明明查询的右端点是12,但是要split(13)呢?
- 是时候瞎扯了 : 由于本人水平有限,以下全为瞎扯 我们联想迭代器,以大家最熟悉的
std::vector
来说,它有两个正向迭代器begin()和end()
,大家都知道begin()
是指向首个元素的迭代器,而end()
呢,它并不指向末尾元素,而是指向末尾元素的下一个元素。这样我们的迭代器循环就是这个形式for(vector<int>::iterator it = v.begin();it!=v.end();++it)
, 因此我们可以直接写出当获取到这两个迭代器后如何遍历区间for(si it = itl;it != itr; ++it)
不过直接split(12),再插入[l,pos]和[pos+1,r],循环改成for(si it = itl;it <= itr; ++it)
,貌似也没错?但我看别人都是split(r+1),或许是为了和STL保持形式一致,便于直接使用STL的内置函数吧,毕竟STL的函数都是包首不包尾的
2.为什么要先分裂右端点,然后再分裂左端点呢?
- 因为如果先分裂左端点,返回的迭代器会位于所对应的区间以
l
为左端点,此时如果r
也在这个节点内,就会导致分裂左端点返回的迭代器被erase
掉,导致 RE - 结合问题1和问题2 ,获取区间迭代器时,务必写成如下格式
si itr = split(r+1), itl = split(l);
起名无所谓,按自己的习惯就好
3.获取到区间之后呢?
- 问的好 获取区间后就可以直接根据操作暴力进行了!
- 可是这样不会退化为暴力吗?
- 额 这就涉及到珂朵莉树的不可或缺的操作了-----将一个区间全部改为某个值。
void assign(int l,int r,ll val){si itr=split(r+1),itl=split(l);//务必先获取r+1,然后再获取ls.erase(itl,itr);//直接删除这两个迭代器之间的所有结点s.insert(node(l,r,val));//插入一个新值的区间
}
回想前文珂朵莉树node结构体的引出,我们是为了用一个点来表示一个区间,这个区间的值自然只能是相同的。那么这样做真的可以加快速度嘛,确实,不难看出上面的assign
可以将多个结点变为一个!
由于题目的数据随机,因此assign
操作大约占到所有操作的0.25,下面我们以一个随机程序,模拟set
中结点的数目:
#include<bits/stdc++.h>
using namespace std;struct node{int l,r;mutable ll val;bool operator<(const node &a)const {return l<a.l;}node(int L,int R,ll Val):l(L),r(R),val(Val){}node(int L):l(L){}
};set<node> s;
using si = set<node>::iterator;si split(int pos){si it = s.lower_bound(node(pos));if(it != s.end() && it->l==pos) return it;--it;int l=it->l,r=it->r;ll val = it->val;s.erase(it);s.insert(node(l,pos-1,val));return s.insert(node(pos,r,val)).first;
}void assign(int l,int r,ll val){si itr=split(r+1),itl=split(l);s.erase(itl,itr);s.insert(node(l,r,val));
}int main() {int n;scanf("%d", &n);for (int i = 1; i <= n + 1; ++i)s.insert(node(i, i, 0));srand((unsigned)time(0));srand(rand());for (int t = 1; t <= n; ++t) {int a = rand() * rand() % n + 1, b = rand() * rand() % n + 1;if (a > b) swap(a, b);if (rand() % 4 == 0) {assign(a, b, 0);}else split(b + 1), split(a);}printf("%d", s.size());return 0;
}
本人电脑实验如下:(n表示序列长度,f(n)表示set
中结点个数)
n | f(n) |
---|---|
10 | 8 |
100 | 21 |
1000 | 36 |
10000 | 55 |
100000 | 54 |
1000000 | 64 |
可见加了assign
的珂朵莉跑的超级快!结点数达到了log级。因此珂朵莉的高效是由随机分配的 assign
来保证的!
其余操作实现
经过上文的 拷打 分析,我们已经不难写出代码了,只需写出 split和assign函数
,其余操作就全是暴力了!!
- 操作一:区间所有数加上 x
//提取区间,暴力加值
void add(int l,int r,ll val){si itr=split(r+1),itl=split(l);for(si it=itl;it!=itr;++it)it->val += val;
}
- 操作三:求区间第k小数
//提取区间,快速排序,暴力查值
ll kth(int l,int r,int k){si itr=split(r+1),itl=split(l);vector<pair<ll,int>> v;v.clear();for(si it=itl;it!=itr;++it)v.push_back(pair<ll,int>(it->val,it->r-it->l+1));sort(v.begin(),v.end());for(int i=0;i<v.size();++i){k -= v[i].second;if(k<=0) return v[i].first;}return -1;//不存在
}
- 操作四:求区间所有数的x次方的和模y的值
//快速幂取模
ll qpow(ll a,int b,ll m){ll t = 1ll;a %= m;while(b){if(b&1) t= (t*a)%m;a = (a*a)%m;b>>=1;}return t;
}
//提取区间,暴力运算
ll query(int l,int r,int x,int y){si itr=split(r+1),itl=split(l);ll res(0);for(si it=itl;it!=itr;++it)res=(res+(it->r-it->l+1)*qpow(it->val,x,y))%y;return res;
}
例题总代码
珂朵莉,推平它!
#include<bits/stdc++.h>
using namespace std;
using ll = long long;struct node{int l,r;mutable ll val;bool operator<(const node &a)const {return l<a.l;}node(int L,int R,ll Val):l(L),r(R),val(Val){}node(int L):l(L){}
};set<node> s;
using si = set<node>::iterator;si split(int pos){si it = s.lower_bound(node(pos));if(it != s.end() && it->l==pos) return it;--it;int l=it->l,r=it->r;ll val = it->val;s.erase(it);s.insert(node(l,pos-1,val));return s.insert(node(pos,r,val)).first;
}void assign(int l,int r,ll val){si itr=split(r+1),itl=split(l);s.erase(itl,itr);s.insert(node(l,r,val));
}void add(int l,int r,ll val){si itr=split(r+1),itl=split(l);for(si it=itl;it!=itr;++it)it->val += val;
}ll kth(int l,int r,int k){si itr=split(r+1),itl=split(l);vector<pair<ll,int>> v;v.clear();for(si it=itl;it!=itr;++it)v.push_back(pair<ll,int>(it->val,it->r-it->l+1));sort(v.begin(),v.end());for(int i=0;i<v.size();++i){k -= v[i].second;if(k<=0) return v[i].first;}return -1;
}ll qpow(ll a,int b,ll m){ll t = 1ll;a %= m;while(b){if(b&1) t= (t*a)%m;a = (a*a)%m;b>>=1;}return t;
}ll query(int l,int r,int x,int y){si itr=split(r+1),itl=split(l);ll res(0);for(si it=itl;it!=itr;++it)res=(res+(it->r-it->l+1)*qpow(it->val,x,y))%y;return res;
}int n, m, vmax;
ll seed;
int rnd() {int ret = (int)seed;seed = (seed * 7 + 13) % 1000000007;return ret;
}
int main() {scanf("%d%d%lld%d", &n, &m, &seed, &vmax);for (int i = 1; i <= n; ++i) {int a = rnd() % vmax + 1;s.insert(node(i, i, (ll)a));}s.insert(node(n + 1, n + 1, 0));for (int i = 1; i <= m; ++i) {int l, r, x, y;int op = rnd() % 4 + 1;l = rnd() % n + 1, r = rnd() % n + 1;if (l > r) swap(l, r);if (op == 3) x = rnd() % (r - l + 1) + 1;else x = rnd() % vmax + 1;if (op == 4) y = rnd() % vmax + 1;if (op == 1) add(l, r, (ll)x);else if (op == 2) assign(l, r, (ll)x);else if (op == 3) printf("%lld\n", kth(l, r, x));else if (op == 4) printf("%lld\n", query(l, r, x, (ll)y));}return 0;
}
小结
经过以上层层 拷打 分析,我们已经了解了珂朵莉!而且我们已经知道了 如果一个题目没有区间赋值操作或者有数据点没有赋值操作,或者数据很不随机,请不要把珂朵莉树当正解打
关于珂朵莉树的复杂度可以前往 -> https://zhuanlan.zhihu.com/p/102786071
其他例题
上啊,珂朵莉
[SCOI2010]序列操作
题目请转->https://ac.nowcoder.com/acm/problem/20279
友情提示,不要尝试去用珂朵莉干了洛谷的这道题目,丧心病狂的 管理已经添加了卡珂朵莉的数据点!所以这里以牛客网的题目为例。
- 题目简述:维护一个01序列,有五种操作:
0 a b 把区间[a,b]内的所有数全变成0
1 a b 把区间[a,b]内的所有数全变成1
2 a b 把[a,b]区间内的所有数全部取反,也就是说把所有的0变成1,把所有的1变成0
3 a b 询问[a, b]区间内总共有多少个1
4 a b 询问[a, b]区间内最多有多少个连续的1
注意到操作0,1全是推平一块区间,珂朵莉狂喜!
- 参考代码:
#include<bits/stdc++.h>
using namespace std;
using ll = long long;struct node{int l,r;mutable int val;bool operator<(const node &a)const {return l<a.l;}node(int L,int R,int Val):l(L),r(R),val(Val){}node(int L):l(L){}
};set<node> s;
using si = set<node>::iterator;si split(int pos){si it = s.lower_bound(node(pos));if(it != s.end() && it->l==pos) return it;--it;int l=it->l,r=it->r;int val = it->val;s.erase(it);s.insert(node(l,pos-1,val));return s.insert(node(pos,r,val)).first;
}void assign(int l,int r,int val){si itr=split(r+1),itl=split(l);s.erase(itl,itr);s.insert(node(l,r,val));
}//区间取反
void negates(int l,int r){si itr=split(r+1),itl=split(l);for(si it=itl;it!=itr;++it)it->val = !it->val;
}
//区间有多少1
int ones(int l,int r){int ans(0);si itr=split(r+1),itl=split(l);for(si it=itl;it!=itr;++it)if(it->val) ans += it->r-it->l+1;return ans;
}
//最长连续的1
int maxseq(int l,int r){int ans(0),cnt(0);si itr=split(r+1),itl=split(l);bool flag(false);for(si it=itl;it!=itr;++it){if(it->val){flag = true;cnt+= it->r-it->l+1;}else if(!it->val && flag){ans = max(ans,cnt);flag = false;cnt = 0;}}ans = max(ans,cnt);//防止区间全是1时,ans在循环里面没来得及更新return ans;
}int n,q;int main() {scanf("%d%d",&n,&q);//题目序列以0下标开始,为了习惯,我们以1开始for(int i=1;i<=n;++i){int a; scanf("%d",&a);s.insert(node(i,i,a));}while(q--){int flag,l,r;scanf("%d %d %d",&flag,&l,&r);//注意本题序列是以0为下标开始的,而我们是以1开始的,所以要手动加1if(flag == 0) assign(l+1,r+1,0);else if(flag == 1) assign(l+1,r+1,1);else if(flag == 2) negates(l+1,r+1);else if(flag == 3) printf("%d\n",ones(l+1,r+1));else if(flag == 4) printf("%d\n",maxseq(l+1,r+1));} return 0;
}
CF817F MEX Queries
题目请转->https://www.luogu.com.cn/problem/CF817F
- 题目简述:维护一个集合,初始为空,有3种操作:
1 把[l,r] 中在集合中没有出现过的数添加到集合中。
2 把[l,r] 中在集合中出现过的数从集合中删掉。
3 把[l,r] 中在集合中没有出现过的数添加到集合中,并把 [l,r] 中在集合中出现过的数从集合中删掉。
每次操作后输出在集合中没有出现过的最小正整数
1≤n≤105,1≤l≤r≤10181 \leq n \leq 10^5 , 1 \leq l \leq r \leq 10^{18}1≤n≤105,1≤l≤r≤1018
简单分析一下各种操作,发现操作一其实就是把一块区间赋值为 1 ,操作二就是把一块区间赋值为 0 ,而操作三就是把一块区间内的1变成0,把0变成1。
然后看一下输出,每次输出集合里面没有出现过的最小正整数,其实就是求第一个0的下标,结合珂朵莉树的性质,直接无脑从1开始往后面搜第一个0即可!
- 参考代码
#include<bits/stdc++.h>
using namespace std;
using ll = long long;#define putlen putchar('\n')//快读
inline ll read(){ll X=0; bool flag=1; char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}if(flag) return X;return ~(X-1);
}
//快输
inline void print(ll x){if(x<0){putchar('-');x=-x;}if(x>9) print(x/10);putchar(x%10+'0');
}struct node{ll l,r;mutable int val;bool operator<(const node &a)const {return l<a.l;}node(ll L,ll R,int Val):l(L),r(R),val(Val){}node(ll L):l(L){}
};set<node> s;
using si = set<node>::iterator;inline si split(ll pos){si it = s.lower_bound(node(pos));if(it != s.end() && it->l==pos) return it;--it;ll l=it->l,r=it->r;int val = it->val;s.erase(it);s.insert(node(l,pos-1,val));return s.insert(node(pos,r,val)).first;
}inline void assign(ll l,ll r,int val){si itr=split(r+1),itl=split(l);s.erase(itl,itr);s.insert(node(l,r,val));
}
//区间0变1,1变0
inline void rever(ll l,ll r){si itr=split(r+1),itl=split(l);for(si it=itl;it!=itr;++it)it->val=!it->val;
}int n;int main(){n=read();s.insert(node(1,1e19,0));while(n--){ll flag=read(),l=read(),r=read();if(flag == 1) assign(l,r,1);else if(flag == 2) assign(l,r,0);else if(flag == 3) rever(l,r);//直接从起点开始暴力向后搜for(si it=s.begin();it!=s.end();++it)if(!it->val){print(it->l),putlen;break;}}return 0;
}
完结撒花
泪目
我的算法不可能这么简单—珂朵莉树相关推荐
- 算法自学__珂朵莉树
参考资料: https://zhuanlan.zhihu.com/p/106353082 https://blog.csdn.net/CC_dsm/article/details/98166835 珂 ...
- [转]我的数据结构不可能这么可爱!——珂朵莉树(ODT)详解
参考资料: Chtholly Tree (珂朵莉树) (应某毒瘤要求,删除链接,需要者自行去Bilibili搜索) 毒瘤数据结构之珂朵莉树 在全是珂学家的珂谷,你却不知道珂朵莉树?来跟诗乃一起学习珂朵 ...
- 浅谈珂朵莉树(Chtholly Tree)——暴力而玄学的数据结构
关于它很珂学的名字- 珂朵莉树(Chtholly Tree),又称老司机树(Old Driver Tree),起源于CodeFoeces平台上编号为896C的一道题-- " Willem, ...
- 数据结构 【树状数组】【线段树】【珂朵莉树】
一.区间合并 1.AcWing245你能回答这些问题吗 分析: 线段树.维护四个变量,即可实现区间合并. mx 区间最大连续子段和 mx_l 以区间左端点为左界的最大连续字段和 mx_r 以区间左端点 ...
- 【日志】珂学——珂朵莉树
珂朵莉树 (珂学) 珂朵莉树(或者老司机树)起源于CF896C. 由于之前做到每一组数据都要另外开数据结构,所以现在一些东西就会写为class包装 前置知识点 STL中set的使用(list也行,但是 ...
- 一种黑科技:珂朵莉树
首先要明白的是:珂朵莉树(ODT)是一种用来骗分的暴力数据结构. 珂朵莉树的思想是利用集合set,把相同且连续的元素合并为一个个区间,从而进行区间修改:因此,珂朵莉树是区间的集合,这点可以通过定义结构 ...
- 珂朵莉树/ODT 学习笔记
珂朵莉树/ODT 学习笔记 起源自 CF896C.珂朵莉yyds! 核心思想 把值相同的区间合并成一个结点保存在 set 里面. 用处 骗分.只要是有区间赋值操作的数据结构题都可以用来骗分.在数据随机 ...
- 浅谈珂朵莉树(ODT)
前言 珂学家狂喜( 文章目录 前言 一.珂朵莉树来源 二.珂朵莉树 1.珂朵莉树有什么用? 2.原理是什么? a.存储 b.分割结点 c.推平 d.剩余操作 3.复杂度分析 三.珂朵莉树例题 1.P4 ...
- 珂朵莉树(永远喜欢珂朵莉/doge)
目录 前言 可能用到前置知识 背景 构建珂朵莉树 核心函数 珂朵莉树在实际题目使用 对珂朵莉树的一些感想 最后的最后 前言 最近刚刚学内容大概是借鉴的吧,感觉这个数据结构不仅简单,还很强,有着非常柯学 ...
最新文章
- git工作区、暂存区和仓库区
- Mysql学习总结(14)——Mysql主从复制配置
- 利用javascript和WebGL绘制地球 【翻译】
- Java黑皮书课后题第5章:**5.28(显示每月第一天是周几)编写程序,提示用户输入年份和代表概念第一天是周几的数字,然后在控制台显示该年各个月份的第一天是周几
- KMP算法的nextval[] 即优化next[]
- 5 Vim编辑器的使用
- POJ-2400 Supervisor, Supervisee 带权值匹配+枚举所有匹配情况
- (回溯 UVa129)困难的串
- 【kafka】Kafka 源码解析:Group 协调管理机制
- 骁龙870对比天玑1200,到底谁更优秀?
- BAT程序员必备技能调研,你中了几招?
- Django修改model如何同步数据库
- 潭州课堂25班:Ph201805201 tornado 项目 第三课 项目 图片上传,展示 (课堂笔记)...
- Response 与 Cookie
- 从零开始学WEB前端——HTML理论讲解
- g++编译so里调用外部so
- linux临时目录不可查询,用find、rm命令清理Linux临时文件夹及检查Linux临时文件夹何时已满...
- 2.12 手机GPS定位
- Mybatis和MybatisPlus3.4的使用
- fcn从头开始_从头开始:简单游戏系列1-抓鱼
热门文章
- 什么是 博客 BLOG 什么是 网摘
- 高德地图api @amap/amap-jsapi-loader封装成方法(定位、点标记、路径规划、搜索等) 适用于vue等框架
- Unity版本捕鱼游戏设计与实现
- MII 类型接口介绍
- 「Python介绍」Python开发环境
- VMware虚拟化之Esxi宿主机内存回收实践
- 原生JS表格排序,实现淘宝商品列按价格要求等排序!
- 统计信号处理:(估计二) 最小方差无偏估计
- drx功能开启后_DRX功能开启指导书(中国移动推荐参数)
- 如何做好营销推广方案?| 品牌营销