浅谈 van Emde Boas 树——从 u 到 log log u 的蜕变
本文参考算法导论完成。
优化的过程比较长,还请读者耐心阅读,认真理解。
最初的想法
我会暴力!
用一个 \(size\) 数组维护每个元素出现的次数。
不细讲,时间复杂度 \(O(um)\)。
树形结构
不难发现,刚刚的算法的本质是将值域划分成了值域大小块。
将 \(0\sim u-1\) 看做一个结点,它有 \(u\) 个儿子,第 \(i\) 个儿子所代表的值域为 \(i-1\sim i-1\)。
给每个结点编号,稍稍改变一下 \(size\) 数组的定义。此时 \(size_i\) 表示第 \(i\) 个结点所代表的值域中每个值出现的次数之和。
可以发现,这是一棵两层的树,除叶子结点外,每个结点都有 \(u\) 个儿子。
而这个算法的瓶颈就在于找出一个结点第一棵、最后一棵有值的子树,一棵子树之前、之后第一棵有值的子树。
那是否可以将结点的儿子数量减小,从而得到更优的复杂度呢?答案是肯定的。
如果儿子变成 \(2\) 个,那这就是一棵权值线段树,瓶颈变成了树的深度,时间复杂度 \(O(m\log u)\)。如果儿子变成 \(\lceil\sqrt u\rceil\) 个,瓶颈依旧在于儿子数量,时间复杂度 \(O(m\sqrt u)\)。
前者会更加优秀,但正是因为后者,才有了之后的改进。
递归结构
总结一下前一节的内容,树形结构遇到了两方面的瓶颈,即树的深度和儿子数量。而我们很难在树的深度上获得什么突破,那就只好限制树的深度了。
考虑采用递归结构,对于每个结点,我们都将它的值域分成值域大小的平方根块,然后递归下去,直到值域大小小于等于 \(2\)。
根据开方的性质,树的深度被限制在了 \(\log\log u\)。
解决完深度后,如何突破儿子数量的瓶颈呢?
考虑对于每一个非叶结点,除了它的儿子,再新建一棵 \(summary\) 子树,结构与其它子树相同,值域大小为其它子树的个数,维护其它子树内是否有值。
注意,这同样是递归结构,\(summary\) 子树内依旧可能会有 \(summary\) 子树。
所以求一个结点第一棵、最后一棵有值的子树,一棵子树之前、之后第一棵有值的子树就转化成了求 \(summary\) 子树内同样的问题,而 \(summary\) 子树的值域大小在原树的基础上有很大的缩减。
于是原型 van Emde Boas 结构就出现了,具体内容会在下一节中介绍。
构建原型 van Emde Boas 结构
令 \(p=\lceil\sqrt u\rceil\)。
定义值域 \(0\sim u-1\) 为一个原型 van Emde Boas 结构或 proto-vEB 结构,记做 \(proto-vEB(u)\),称一个结构为一个簇。
\(u=2\),\(proto-vEB(u)\) 是叶子结点,包含一个大小为 \(2\) 的数组 \(a_{0\sim 1}\),表示值 \(0\) 和 \(1\) 的出现次数。
\(u>2\),\(proto-vEB(u)\) 包含一个大小为 \(\lceil\frac{u}{p}\rceil\) 的数组 \(cluster_{0\sim\lceil\frac{u}{p}\rceil-1}\) 表示 \(proto-vEB(u)\) 的 \(\lceil\frac{u}{p}\rceil\) 个子簇的编号,前 \(\lfloor\frac{u}{p}\rfloor\) 个子簇都是一个 \(proto-vEB(p)\),如果最后多出来的一个子簇存在那么它的大小为 \(\max(u\bmod p,2)\)。
另外,\(proto-vEB(u)\) 还包含一个大小为 \(\lceil\frac{u}{p}\rceil\) 的子簇 \(summary\),\(summary\) 也是一个 \(proto-vEB\) 结构。
为了方便快捷地支持删除值后 \(summary\) 的更新问题(不能直接删,因为对应簇中可能有多个值),\(proto-vEB(u)\) 还有一个 \(size\) 表示值域中每个值出现的次数之和。
但这样会出现一个新问题:如何得知当前值所在的位置?或者说,如何得知某个位置所代表的值?
首先注意一点:值都是相对于当前簇的,要知道真实的意义必须向上运算至最近的 \(summary\) 的顶部(此时是对上一层值的一个统计)或整个簇(全域)的顶部(此时是真实元素)。
考虑这个运算。第 \(x(0\leq x<\lceil\frac{u}{p}\rceil)\) 个子簇中值 \(y\) 相对于当前层的值是 \(xp+y\)。
反过来也一样,当前层的值 \(x\) 应该在第 \(\lfloor\frac{x}{p}\rfloor\) 个子簇中,相对于这个子簇的值为 \(x\bmod p\)。
理清楚这些概念后,就可以来康代码了。
proto-vEB 结构:
struct proto_vEB
{
vector<int>clu;
int a[2],u,sqr,sum,siz;
}t[2001005];
因为 \(cluster\) 的大小不确定所以我们用了个 vector
来实现。
建树:
void create(proto_vEB&v,int u)
{
v.u=Max(u,2);
if(u>2)
{
int i;
v.sqr=int(sqrt(Db(u)));
if(v.sqr*v.sqr<u)v.sqr++;
For(i,1,u/v.sqr)v.clu.push_back(++cnt),create(t[cnt],v.sqr);
if(u%v.sqr)v.clu.push_back(++cnt),create(t[cnt],u%v.sqr);
v.sum=++cnt;
create(t[cnt],u/v.sqr+(u%v.sqr>0));
}
}
用到的结点数量是 \(S(u)=S(\lceil\sqrt u\rceil)\times\lfloor\frac{u}{\lceil\sqrt u\rceil}\rfloor+S(u\bmod\lceil\sqrt u\rceil))+S(\lceil\frac{u}{\lceil\sqrt u\rceil}\rceil)+1\),这东西还不是单调的。有没有鸽鸽能来指教一下 \(1\sim u\) 的上界大概是多少啊/kel
原型 van Emde Boas 结构上的操作
有点复杂,直接对着代码讲解。
为了方便,我们首先用三个函数表示上文提到过的运算。
inline int high(proto_vEB&v,int x)
{
return x/v.sqr;
}
inline int low(proto_vEB&v,int x)
{
return x%v.sqr;
}
inline int index(proto_vEB&v,int x,int y)
{
return x*v.sqr+y;
}
查询某个值出现的次数
int member(proto_vEB&v,int x)
{
if(v.u==2)return v.a[x];
return member(t[v.clu[high(v,x)]],low(v,x));
}
第三行处理叶子结点的情况。
第四行在 \(x\) 所在的子簇内继续查找。
查找簇内最小值
int minimum(proto_vEB&v)
{
if(v.u==2)return(v.a[0]?0:(v.a[1]?1:-1));
int mn=minimum(t[v.sum]);
return(!~mn?-1:index(v,mn,minimum(t[v.clu[mn]])));
}
第三行处理叶子结点的情况。
第四行查找第一个有值的子簇。
第五行在该子簇内继续查找。
查找簇内最大值
int maximum(proto_vEB&v)
{
if(v.u==2)return(v.a[1]?1:(v.a[0]?0:-1));
int mx=maximum(t[v.sum]);
return(!~mx?-1:index(v,mx,maximum(t[v.clu[mx]])));
}
与最小值对称。
查找簇内某个值的前驱
int predecessor(proto_vEB&v,int x)
{
if(v.u==2)return(x&&v.a[0]?0:-1);
int rem=predecessor(t[v.clu[high(v,x)]],low(v,x));
if(~rem)return index(v,high(v,x),rem);
rem=predecessor(t[v.sum],high(v,x));
return(!~rem?-1:index(v,rem,maximum(t[v.clu[rem]])));
}
第三行处理叶子结点的情况。
第四行在 \(x\) 所在的子簇内查找前驱。
第五行判断是否找到,找到了就直接返回。
第六行在前面的子簇中查找最近的有值的子簇。
第七行在该子簇内查找最大值。
查找簇内某个值的后继
int successor(proto_vEB&v,int x)
{
if(v.u==2)return(!x&&v.a[1]?1:-1);
int rem=successor(t[v.clu[high(v,x)]],low(v,x));
if(~rem)return index(v,high(v,x),rem);
rem=successor(t[v.sum],high(v,x));
return(!~rem?-1:index(v,rem,minimum(t[v.clu[rem]])));
}
与前驱对称。
插入一个值
int insert(proto_vEB&v,int x,bool rem)
{
if(v.u==2)if(rem)
{
v.a[x]++;
return 1;
}
else
{
if(v.a[x])return 0;
v.a[x]=1;
return 1;
}
else
{
int p=insert(t[v.clu[high(v,x)]],low(v,x),rem),q=insert(t[v.sum],high(v,x),0);
t[v.clu[high(v,x)]].siz+=p;
t[v.sum].siz+=q;
return p+q;
}
}
该函数的返回值表示该子簇内新插入的值的数量。
第三至十三行处理叶子结点的情况。
\(rem\) 表示当前簇是否处于某个 \(summary\) 内。
如果没有,说明此处存储的是某个元素出现的次数,可以大于 \(1\)。
否则仅仅需要判断是否出现,不可以大于 \(1\)。
第十六行往 \(x\) 所在的子簇和 \(summary\) 内插入对应的值,同时统计新插入的值的数量。
第十七和十八行更新 \(size\)。
第十九行返回当前簇新插入的值的数量。
删除一个值
int Delete(proto_vEB&v,int x)
{
if(v.u==2)if(v.a[x])
{
v.a[x]--;
return 1;
}
else return 0;
else
{
int p=Delete(t[v.clu[high(v,x)]],low(v,x)),q=0;
t[v.clu[high(v,x)]].siz-=p;
if(!t[v.clu[high(v,x)]].siz)q=Delete(t[v.sum],high(v,x));
t[v.sum].siz-=q;
return p+q;
}
}
该函数的返回值表示该子簇内新删除的值的数量。
第三至八行处理叶子结点的情况。
如果 \(a_x>0\) 说明可以删除就删除返回 \(1\),否则返回 \(0\)。
第十一行去 \(x\) 所在的子簇中删除对应的值,同时统计新删除的值的数量。
第十二行更新 \(x\) 所在的子簇的 \(size\)。
第十三行判断 \(x\) 所在的子簇是否为空,为空就在 \(summary\) 中删除对应的值,同时统计新删除的值的数量。
第十四行更新 \(summary\) 的 \(size\)。
第十五行返回当前簇新删除的值的数量。
时间复杂度分析
member
:\(T(u)=T(\sqrt u)+O(1)\),花费 \(O(\log\log u)\)。
minimum
maximum
insert
Delete
:\(T(u)=2T(\sqrt u)+O(1)\),花费 \(O(\log u)\)。
predecessor
successor
:\(T(u)=2T(\sqrt u)+O(\log u)\),花费 \(O(\log u\log\log u)\)。
还需要继续改进。
van Emde Boas 结构成型
发现 proto-vEB 不够优秀的原因是因为有些操作我们需要多次递归,我们想要尽可能减少递归。
考虑多记录两个量,簇内的最小值 \(mn\) 和最大值 \(mx\)。
需要注意的是,\(mn\) 不出现在簇中,而 \(mx\) 出现在簇中,即该簇中(不包含该簇)不会有任何一个 \(mn\) 或 \(mx\) 等于该簇的 \(mn\),该簇中某个簇(包含该簇)的 \(mn\) 一定会等于该簇的 \(mx\)。
簇的 \(mn\) 与 \(mx\) 不包含 \(summary\) 中的值。
这样做是有原因的:
minimum
和maximum
甚至不需要递归,可以直接用。predecessor
和successor
可以直接判断前驱和后继是否在 \(x\) 所在的子簇中,不需要递归。可以 \(O(1)\) 判断一个簇的状态。如果 \(mn\) 和 \(mx\) 都不存在,那么簇为空;如果 \(mn=mx\),那么簇仅包含一个值;如果 \(mn\) 和 \(mx\) 不相等,那么簇包含至少两个值。
可以 \(O(1)\) 将一个值插入一个空簇中。
van Emde Boas 结构上的操作
细节很多,依旧来看每个操作。
查询某个值出现的次数
int member(vEB&v,int x)
{
return(x==v.mn?v.a[0]:(x==v.mx?v.a[1]:(v.u==2?0:member(t[v.clu[high(v,x)]],low(v,x)))));
}
第三行先判断是否是簇内值的个数小于等于 \(2\) 或叶子结点的情况,是就返回,不是就在 \(x\) 所在的子簇内继续查找。
查找簇内某个值的后继
int successor(vEB&v,int x)
{
if(v.u==2)return(!x&&v.mx==1?1:-1);
if(x<v.mn)return v.mn;
int rem=t[v.clu[high(v,x)]].mx;
if(~rem&&low(v,x)<rem)return index(v,high(v,x),successor(t[v.clu[high(v,x)]],low(v,x)));
rem=successor(t[v.sum],high(v,x));
return(!~rem?-1:index(v,rem,t[v.clu[rem]].mn));
}
第三行处理叶子结点的情况。
第四行处理后继是 \(mn\) 的情况。
第六行判断后继是否在 \(x\) 所在的子簇内,是就在 \(x\) 所在的子簇内继续查找后继。
第七行在之后的子簇内查找最近有值的子簇。
第八行返回该子簇的 \(mn\)。
查找簇内某个值的前驱
int predecessor(vEB&v,int x)
{
if(v.u==2)return(x&&!v.mn?0:-1);
if(~v.mx&&x>v.mx)return v.mx;
int rem=t[v.clu[high(v,x)]].mn;
if(~rem&&low(v,x)>rem)return index(v,high(v,x),predecessor(t[v.clu[high(v,x)]],low(v,x)));
rem=predecessor(t[v.sum],high(v,x));
return(!~rem?(v.mn<x?v.mn:-1):index(v,rem,t[v.clu[rem]].mx));
}
同后继,但要注意一种情况,即之前的子簇中都无值。
此时 \(x\) 的前驱可能是 \(mn\)(因为 \(mn\) 不在簇中嘛),在第八行中多判断了一下。
插入一个值
void insert(vEB&v,int x,int y,bool bo)
{
if(!bo)y=1;
if(!~v.mn)
{
v.mn=v.mx=x;
v.a[0]=v.a[1]=y;
return;
}
if(x==v.mn)
{
if(bo)
{
v.a[0]+=y;
if(v.mn==v.mx)v.a[1]+=y;
}
return;
}
if(x<v.mn)
{
if(v.mn==v.mx&&bo)v.a[1]=0;
swap(x,v.mn),swap(v.a[0],y);
}
if(v.u>2)
{
int rem=v.clu[high(v,x)];
if(!~t[rem].mn)insert(t[v.sum],high(v,x),y,0);
insert(t[rem],low(v,x),y,bo);
}
if(x>v.mx)v.mx=x,v.a[1]=y;
else if(x==v.mx&&bo)v.a[1]+=y;
}
既然都能直接判断簇的状态了,那还要 \(size\) 何用?但是代码更长了(悲
我们依旧记录当前簇是否处于某个 \(summary\) 内,用 \(bo\) 表示。另外,\(y\) 表示插入值的个数。
第三行因为 \(summary\) 中值出现次数只能为一次,所以处理一下。
第四至九行处理簇内无值的情况。注意 \(mn\) 和 \(mx\) 的出现次数都要变成 \(y\)!
第十至十八行处理 \(x=mn\) 的情况。
第十二行判断是否在 \(summary\) 内,只有不在 \(summary\) 内才能增加出现次数(原先出现次数至少为 \(1\))。
第十四行更新 \(mn\) 的出现次数。
第十五行注意当 \(mn=mx\) 时同时更新 \(mx\) 的出现次数!
第十九至二十三行处理 \(x<mn\) 的情况。
第二十一行插个眼。
第二十二行更新 \(mn\)。注意是交换,因为如果 \(mn\) 被更新了,原先的 \(mn\) 依旧还存在,要传下去。且传下去必定能插入,因为相同的值没有被传下去过。
第二十四行判断是否是叶子结点,不是才能继续插下去。
第二十七行在 \(x\) 所在子簇为空的情况下更新 \(summary\)。
第二十八行去 \(x\) 所在的子簇内插。
第三十行更新 \(mx\)。
第三十一行更新 \(mx\) 的出现次数,此处拔起第二十一行的眼。我们发现执行第二十一行时互换了 \(x\) 和 \(mn\),如果原先 \(mn=mx\),那第三十一行实际出现次数没有变化的 \(mx\)(即原 \(mn\))的出现次数会重复叠加。所以要在第二十一行特判这种情况,提前将 \(mx\) 的出现次数清空,这样就不会受到影响了。
删除一个值
void Delete(vEB&v,int x,bool bo)
{
if(v.mn==v.mx)
{
if(!~v.mn)return;
if(x!=v.mn)return;
if(bo||v.a[0]==1)v.mn=v.mx=-1;
else v.a[0]--,v.a[1]--;
return;
}
if(v.u==2)
{
if(!x)if(bo||v.a[0]==1)v.mn=1,v.a[0]=v.a[1];
else v.a[0]--;
else if(bo||v.a[1]==1)v.mx=v.mn,v.a[1]=v.a[0];
else v.a[1]--;
return;
}
int rem;
if(x==v.mn)
{
if(!bo&&v.a[0]>1)
{
v.a[0]--;
return;
}
rem=t[v.sum].mn;
x=index(v,rem,t[v.clu[rem]].mn);
v.mn=x;
v.a[0]=t[v.clu[rem]].a[0];
bo=1;
}
rem=v.clu[high(v,x)];
Delete(t[rem],low(v,x),bo);
if(!~t[rem].mn)
{
Delete(t[v.sum],high(v,x),bo);
if(x==v.mx)
{
if(!bo&&v.a[1]>1)
{
v.a[1]--;
return;
}
rem=t[v.sum].mx;
if(!~rem)v.mx=v.mn,v.a[1]=v.a[0];
else v.mx=index(v,rem,t[v.clu[rem]].mx),v.a[1]=t[v.clu[rem]].a[1];
}
}
else if(v.mx==x)
{
if(!bo&&v.a[1]>1)
{
v.a[1]--;
return;
}
rem=v.clu[high(v,x)];
v.mx=index(v,high(v,x),t[rem].mx);
v.a[1]=t[rem].a[1];
}
}
真就长那么亿点点(
实在讲不动了,就自己理解吧,能有耐心看到这我相信各位都能自己写出来了。我只把坑点说一下。
首先,如果删除后更新了 \(mn\),那么要将簇中存在的最小值变成 \(mn\),然后删除它。此时要删完,即该值不管出现几次都清空,因为次数也跟着变成 \(mn\) 的出现次数了。所以我们用一个 \(bo\) 表示是否要删完。
第七行要同时清空 \(mn\) 和 \(mx\)。
第八行要同时更新 \(mn\) 和 \(mx\) 的次数。
第十三和十五行更新 \(mn\) 或 \(mx\) 时记得同时更新出现次数。
第三十一行在更新完 \(mn\) 后记得将 \(bo\) 置为 \(1\)。
第三十八行记得更新 \(mx\),此处的情况是 \(x\) 所在的子簇内已无值,\(mx\) 为前面最后一个有值的子簇的 \(mx\)。
第五十行记得更新 \(mx\),此处的情况是 \(x\) 所在的子簇内还有值,\(mx\) 直接为该子簇的 \(mx\)。
时间复杂度分析
member
:\(T(u)=T(\sqrt u)+O(1)\),花费 \(O(\log\log u)\)。
predecessor
successor
:\(T(u)=T(\sqrt u)+O(1)\),花费 \(O(\log\log u)\)。
insert
Delete
:虽然看起来有两次调用,但分析后会发现有一个只需要花费常数时间,所以 \(T(u)=T(\sqrt u)+O(1)\),花费 \(O(\log\log u)\)。
总时间复杂度 \(O(m\log\log u)\),非常优秀。
后记
这个算法我啃了好几天,虽然在 OI 中用处不大,但也算是自己的一个增长点吧。
另外,这个算法的思想和改进思路也是值得借鉴的。限制一部分,优化一部分。
在优化这方面,为什么会想到用值域的平方根来作为下一层的大小呢?其实这取决于 \(summary\)。在想到用 \(summary\) 来优化同一层的查找后,递归就分成了两部分,一部分是 \(summary\),另一部分是 \(summary\) 中每个值对应的值域区间。这两部分当然尽可能均匀越好喽,所以就取了平方根。
最后成型的 \(mn\) 和 \(mx\) 让查询变得非常快速,\(mn\) 不会出现在簇中让更新变得非常快速,不得不说非常非常巧妙。
只剩必要的一条递归。
将信息利用到了极致。
浅谈 van Emde Boas 树——从 u 到 log log u 的蜕变的更多相关文章
- BZOJ 3685: 普通van Emde Boas树( 线段树 )
建颗权值线段树就行了...连离散化都不用... 没加读入优化就TLE, 加了就A掉了...而且还快了接近1/4.... ---------------------------------------- ...
- bzoj3685普通van Emde Boas树 线段树
3685: 普通van Emde Boas树 Time Limit: 9 Sec Memory Limit: 128 MBSubmit: 1932 Solved: 626[Submit][Stat ...
- BZOJ_3685_普通van Emde Boas树_权值线段树
BZOJ_3685_普通van Emde Boas树_权值线段树 Description 设计数据结构支持: 1 x 若x不存在,插入x 2 x 若x存在,删除x 3 输出当前最小值,若不存 ...
- bzoj 3685: 普通van Emde Boas树
3685: 普通van Emde Boas树 Description 设计数据结构支持:1 x 若x不存在,插入x2 x 若x存在,删除x3 输出当前最小值,若不存在输出-14 输出当 ...
- 【bzoj3685】普通van Emde Boas树 线段树
普通van Emde Boas树 Time Limit: 9 Sec Memory Limit: 128 MBSubmit: 1969 Solved: 639[Submit][Status][Di ...
- 算法导论笔记——第二十章 van Emde Boas树
当关键字是有界范围内的整数时,能够规避Ω(lglgn)下界的限制,那么在类似的场景下,我们应弄清楚o(lgn)时间内是否可以完成优先队列的每个操作.在本章中,我们将看到:van Emde Boas树支 ...
- 【BZOJ3685】【zkw权值线段树】普通van Emde Boas树
原题传送门 因为马上要开始搞树套树了,所以学了一波权值线段树...毕竟是会点zkw线段树的,所以zkw线段树大法好! 解题思路: 介绍一下权值线段树吧,其实感觉就是线段树的本义,就是你用线段树维护了数 ...
- 【模板】BZOJ 3685: 普通van Emde Boas树——Treap
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3685 据说神犇都是用zkw线段树水过的啊... 我蒟蒻只会写treap,加了fread之后8 ...
- 【权值分块】bzoj3685 普通van Emde Boas树
权值分块,虽然渐进复杂度不忍直视,但其极小的常数使得实际运行起来比平衡树快,大多数情况和递归版权值线段树差不多,有时甚至更快.但是被zkw线段树完虐. #include<cstdio> # ...
随机推荐
- sql server DDL语句 建立数据库 定义表 修改字段等
一.数据库:1.建立数据库 create database 数据库名;use 数据库名; create database exp1;use exp1; mysql同样 2.删除数据库 drop dat ...
- Iperius Backup Full--小中企业简单自动备份的实用工具
从事IT行业几个年头了,一直以来发现备份这个词是十分特殊的.无论是事业国有大企央企还是个人爱好者,小型工作室,中小企业. 对于备份都是明确知道十分重要,但在正在实施起来会因为投入,领导重视程度,实施管 ...
- 【译】Rust中的array、vector和slice
原文链接:https://hashrust.com/blog/arrays-vectors-and-slices-in-rust/ 原文标题:Arrays, vectors and slices in ...
- cookie与session的概念与区别
会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话.常用的会话跟踪技术是Cookie与Session.Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端 ...
- git版本管理系统使用
版本管理系统Git 关注公众号"轻松学编程"了解更多. git下载链接:https://pan.baidu.com/s/12vJn-K0lK9XlkVQbNe8S-A 密码:m4m ...
- 探索G1垃圾回收器
前言 最近王子因为个人原因有些忙碌,导致文章更新比较慢,希望大家理解,之后也会持续和小伙伴们一起共同分享技术干货. 上篇JVM的文章中我们对ParNew和CMS垃圾回收器已经有了一个比较透彻的认识,感 ...
- 实用fork/join框架提升程序效率
实用fork/join框架提成程序效率 原文地址:https://www.jianshu.com/p/9ce243796d4a 业务场景 最近再做一个接口,我是一个中央的消息接受方,当我接受到消息后要 ...
- 获取List集合对象中某一列属性值
例:获取disposeList集合中CorpusMarkPage对象中的responseId属性,生成新的List集合 List<String> responseIdList = disp ...
- 从比心APP源码的成功,分析陪玩系统源码应该如何开发
提起游戏陪玩系统,相信大家都不陌生.作为一名骨灰级的手游玩家,小编对于陪玩系统源码也有些了解.在互联网络发展愈发迅速的今天,游戏产业在一中领域中脱颖而出,据统计,手机游戏用户已经达到5.29亿,较20 ...
- python数据分析01准备工作
第1章 准备工作 1.1 本书的内容 本书讲的是利用Python进行数据控制.处理.整理.分析等方面的具体细节和基本要点.我的目标是介绍Python编程和用于数据处理的库和工具环境,掌握这些,可以让你 ...