连环病原体 - 题解
题目链接
题意:一张 $ n $ 点 $ m $ 边无向图,每条边有编号。若一个区间内的边能连成一个环,则称这个区间为好区间。求每条边分别在多少个好区间内。
算法一:
首先想到一个暴力。枚举区间的左端点,右端点,用并查集判断是否有环,得分 $ 60 $ 分。
算法二:
显然的优化1:设 $ [l, r] $ 表示从 $ l $ 到 $ r $ 的区间满足条件且从 $ l $ 到 $ r - 1 $ 的区间不满足条件。 $ l $ 递增,显然 $ r $ 不降。
显然的优化2:对于统计答案,记录首项和公差,最后跑一边前缀和。
对于优化1,我们需要一个可以支持加边和删边的并查集, $ LCT $ 即可。
得分 $ 90 $ ~ $ 100 $ 分( $ findroot $ 时将结果 $ splay $ 试试?)。
#include<bits/stdc++.h>
#define lc ch[u][0]
#define rc ch[u][1]
using namespace std;
const int N=400010;
typedef long long ll;
int m,a[N],b[N];
int fa[N],ch[N][2],sta[N];
bool lz[N],flag;
ll ans[N],dl[N];
inline bool nroot(int u) { return ch[fa[u]][0]==u||ch[fa[u]][1]==u; }
void pushdown(int u) { if(lz[u]) swap(lc,rc),lz[lc]^=1,lz[rc]^=1,lz[u]=0; }
inline void pushup(int u) { }
void rotate(int u) {
int y=fa[u],z=fa[y],k=ch[y][1]==u,w=ch[u][k^1];
if(nroot(y)) ch[z][ch[z][1]==y]=u; ch[y][k]=w,ch[u][k^1]=y;
if(w) fa[w]=y; fa[y]=u,fa[u]=z;
pushup(y),pushup(u);
}
void splay(int u) {
int y=u,z,top=1;sta[top]=y;while(nroot(y)) sta[++top]=y=fa[y];
while(top) pushdown(sta[top--]);
for(;nroot(u);rotate(u)) {
y=fa[u],z=fa[y];
if(nroot(y)) rotate((ch[y][0]==u)^(ch[z][0]==y)? u:y);
}
}
void access(int u) { for(int y=0;u;u=fa[y=u]) splay(u),rc=y,pushup(u); }
void makeroot(int u) { access(u),splay(u),lz[u]^=1; }
int findroot(int u) {
access(u),splay(u),pushdown(u);
while(lc) u=lc,pushdown(u); splay(u);return u;
}
void split(int x,int y) { makeroot(x),access(y),splay(y); }
void lnk(int x,int y) { makeroot(x),fa[x]=y; }
void cut(int x,int y) { split(x,y),ch[y][0]=fa[x]=0,pushup(y); }
int main() {
scanf("%d",&m);
for(int i=1;i<=m;i++) scanf("%d%d",&a[i],&b[i]);
for(int l=1,r=0;l<=m;++l) {
flag=0;
while(r<m) {
++r;
if(findroot(a[r])==findroot(b[r])) { flag=1;break; }
lnk(a[r],b[r]);
}
if(!flag) break;
ans[l]=m-r+1,--dl[r+1],--r,cut(a[l],b[l]);
}
ll sum=0;
for(int i=1;i<=m;i++) sum+=dl[i],ans[i]=ans[i-1]+ans[i]+sum;
for(int i=1;i<=m;i++) printf("%lld ",ans[i]);
return 0;
}