题解 P9201 「GMOI R2-T4」电子木鱼
LCT 维护基环树森林的题似乎不常见,写篇题解记录一下。
题目大意
有
不断进行以下操作:
- 取出编号最小的
S 为空的元素(d_*,S_*) - 把所有的元素的
S 替换成S\oplus \{d_*\}
问最少在末尾添加几个元素可以使操作无限进行。
对每个
先考虑单个询问。
状压集合,然后操作变成每个
由于是全局异或,显然只需要关心目前异或的总量。
并且,对一个总异或和为
思考加一个元素的直观感受,显然新加元素的
而操作能无限进行,就相当于从
现在可以考虑求解答案了:
-
-
- $0$ 所在弱连通块大小 $\ge 2$,那么答案为 $1$。 - $0$ 为一个孤点: - 若某个 $\operatorname{popcnt}=1$ 的点在一个内向基环树中,那么答案为 $1$。 - 否则,答案为 $2$。
这样,枚举
int Find(int x){ return fa[x]==x?x:fa[x]=Find(fa[x]); }
void Union(int x, int y){
x=Find(x),y=Find(y);
if (x!=y) fa[x]=y,siz[y]+=siz[x],cnt[y]+=cnt[x],S[y]|=S[x];
cnt[y]--;
flag|=cnt[y]==0 && S[y];
}
int main(){
scanf("%d%d",&n,&m); pw[0]=1;
for (int i=1; i<=n+1; i++) pw[i]=pw[i-1]*2%mod;
for (int i=1; i<=n; i++){
scanf("%d%s",&d[i],s);
for (int j=0; j<m; j++) sta[i]|=(s[j]-'0')<<j;
}
for (int i=1; i<=n; i++){
flag=0;
for (int j=0; j<(1<<m); j++) vis[j]=0,fa[j]=j,cnt[j]=siz[j]=1,S[j]=__builtin_popcount(j)==1;
for (int j=i; j<=n; j++){
if (!vis[sta[j]]) Union(sta[j]^(1<<(d[j]-1)),sta[j]);
int u=Find(0); vis[sta[j]]=1;
if (siz[u]==1) ans=(ans+(flag?pw[i]:pw[i+1]))%mod;
else if (cnt[u]) ans=(ans+pw[i])%mod;
else break;
}
}
printf("%d\n",ans);
}
然后考虑正解。
-
- 否则,正常连边,连边后整个弱连通块的根定为原
t 所在弱连通块的根。
注意 Link,Cut 均有可能改变树根,因此在做完 LCT 操作后都要注意恢复正确的树根。.
最后一部分是查询:
- 先查出
u 所在弱连通块的根,若其没有附加边则为内向树,返回n+1 。 - 否则,求出
u 到根路径的点权\max ,和环上点权\max ,取大的那个返回即可。
时间复杂度为
代码有相当的细节,而且考场代码相当丑陋,省略了标准 LCT 操作。
inline void upd(int u, int x, int i){
if (To[u]){
if ((u==1 || To[u]==1) && w[u]) S.erase(w[u]); //w[u] 为点权,To[u] 为 u 的出边
int rt=Findroot(u);
if (u==rt) E[u]=0; //E 为附加边指向的另一个节点
else if (!E[rt]){
int tmp=Findroot(To[u]);
Cut(u,To[u]); makeroot(tmp);
} else {
access(E[rt]),splay(E[rt]); int k=u;
while (nroot(k)) k=fa[k];
if (k==E[rt]) Cut(u,To[u]),Link(rt,E[rt]),E[rt]=0,makeroot(u);
else {
int tmp=Findroot(To[u]);
Cut(u,To[u]); makeroot(tmp);
}
}
}
To[u]=x;
if (Findroot(To[u])==Findroot(u)) makeroot(u),E[u]=To[u];
else {
int tmp=Findroot(To[u]);
Link(u,To[u]); makeroot(tmp);
}
splay(u); w[u]=i; pushup(u);
if (u==1 || To[u]==1) S.insert(w[u]);
}
inline int qry(int u){
int rt=Findroot(u);
if (!E[rt]) return n+1;
else {
access(u),splay(u); int ret=val[u]; //val 为维护出来的实链点权 max
access(E[rt]),splay(E[rt]); ret=max(ret,val[E[rt]]);
return ret;
}
}
int main(){
scanf("%d%d",&n,&m); pw[0]=1;
for (int i=1; i<=n; i++) pw[i]=pw[i-1]*2%mod;
for (int i=1; i<=n; i++){
scanf("%d%s",&d[i],s);
for (int j=0; j<m; j++) sta[i]|=(s[j]-'0')<<j;
}
for (int i=n; i>=1; i--){
upd(sta[i]+1,(sta[i]^(1<<(d[i]-1)))+1,i);
int tim=qry(1),k=n+1;
for (int j=0; j<m; j++) k=min(k,qry((1<<j)+1));
ans=(ans+1ll*pw[i]*(tim-i))%mod;
int mn=min(k,(S.empty()?n+1:*S.begin()));
ans=(ans+1ll*pw[i]*(mn-i))%mod;
}
printf("%d\n",ans);
}