题解 【P6580 [Ynoi2019] 美好的每一天~ 不连续的存在】
command_block · · 题解
题意 : 给出一棵
有
允许离线,
使用回滚莫队。
注意到权值数组是不规则的,只能采取较为暴力的做法。
-
bitset!
每个点的复杂度权重为其度数,按权重将序列分块。
用 bitset 维护颜色出现次数
回滚莫队将问题转化为
还有个问题,
-
线段树合并!
我们发现,回滚莫队的右指针单调向右移动,此处发生的合并可以用线段树合并维护(还有并查集),于是一趟从左到右的合并被优化到了
处理左指针时,可以沿用线段树合并,保留副本,
左指针的复杂度是暴力吗?
考虑“向集合
于是,将
按照这个权重来分块,即可做到
这个复杂度看起来很对劲,但由于线段树合并的常数较大,难以通过。
-
启发式合并!位运算魔改线段树!
把线段树合并换成启发式合并,复杂度同样是正确的。注意到按秩合并并查集也是启发式合并,这样实现会很方便。
但我们似乎并没有合适的数据结构。std::set 的启发式合并是
注意到
这个数据结构有三层,是一个树形结构。
底层是若干 uint ,用于保存颜色出现次数的奇偶性。
第二层每个节点分为 uint 记录每个分支中是否有值。
将二三层绑定在一起,不动态开点,这样第二层就不需要记录儿子指针,可以直接
第一层分为 10 叉,同样用一个 uint 记录每个分支中是否有值。
不同于第二层的是,为了节省空间,此处需要记录儿子指针。
能够发现,单元素集合占用的空间是“一个第二层节点与其子树”,是 uint 。而树根占用的空间是
将集合
-
对于第一层,若
S_1 有某个分支且S_2 没有,则拷贝指针。 -
若两个第二层合并,容易用第二层上的
uint找到所有需要操作的位置。
下面给出这个数据结构的实现 :
const uint Bas=4294967295u;
struct BitBlo{
//第二层和第三层
int cnt;uint rt,buf[32];
void clear(){
while(rt){
int p=__builtin_ctz(rt);
rt^=(1u<<p);buf[p]=0;
}cnt=0;
}
void build(int x){
rt=(1u<<(x>>5));
buf[x>>5]=(1u<<(x&31));
cnt=1;
}
}T[MaxN];int tot;
void Sxor(BitBlo &A,const BitBlo &B)
{
//合并两个第二层节点
uint rt=A.rt&B.rt,rt2=(Bas^A.rt)&B.rt;
while(rt){
int p=__builtin_ctz(rt);rt^=(1u<<p);
A.cnt-=__builtin_popcount(A.buf[p]);
A.cnt+=__builtin_popcount(A.buf[p]^=B.buf[p]);
if (!A.buf[p])A.rt^=(1u<<p);
//在用 xor 代替撤回时,这个删除判断是必须的
}
while(rt2){
int p=__builtin_ctz(rt2);rt2^=(1u<<p);
A.cnt+=__builtin_popcount(A.buf[p]=B.buf[p]);
A.rt|=(1u<<p);
}
}
struct Bitset{
//第一层
uint rt;int t[10],cnt;
void clear(){
while(rt){
int p=__builtin_ctz(rt);
rt^=(1u<<p);t[p]=0;
}cnt=0;
}
inline void build(int x){
T[t[x>>10]=++tot].build(x&1023);
rt=(1u<<(x>>10));cnt=1;
}
}S[MaxN];
void Sxor(Bitset &A,const Bitset &B)
{
//合并两棵树
uint rt=A.rt&B.rt,rt2=(Bas^A.rt)&B.rt;
while(rt){
int p=__builtin_ctz(rt);rt^=(1u<<p);
if (A.t[p]==B.t[p]){
A.cnt-=T[A.t[p]].cnt;
A.t[p]=0;A.rt^=(1u<<p);
//这只会在用 xor 代替撤回时触发,将拷贝过去的指针撤回
}else {
A.cnt-=T[A.t[p]].cnt;
Sxor(T[A.t[p]],T[B.t[p]]);
A.cnt+=T[A.t[p]].cnt;
}
}
while(rt2){
int p=__builtin_ctz(rt2);rt2^=(1u<<p);
A.cnt+=T[A.t[p]=B.t[p]].cnt;
A.rt|=(1u<<p);
}
}
有点卡常,我卡了一个下午,后来晚高峰搭神机才过……
又优化了一下写法(去掉了原本的 ull),可以做到最大点 2.35s 。
根据 Ynoi 传统,不给出完整代码。