题解 P11674 [USACO25JAN] Reachable Pairs G
Gold 组唯一过的一道题。
题意
参见题目描述。
分析
移除节点并不方便维护连通性,考虑倒序添加用并查集维护。
考虑移除这两点操作的本质是什么。
所有 \(s_t = 0\) 的情况是比较好做的,直接加边即可。
- \(s_t = 0\),删除这个点并断开和这个点相连的所有边,倒序处理时只需要加上这个点并和所有边相连。
- \(s_t = 1\),移除这个点但是其他点的连通性却没有改变,也就是说这个“实点”转变成了“虚点”,反着做只需要给这个连通块的点数加一。
在开始询问以前只需要添加连接两个虚点的边即可。
使用按秩合并和路径压缩并查集维护总点数和实点数,时间复杂度 \(O(N+M)\)。
代码
//the code is from chenjh
#include<cstdio>
#include<vector>
#define MAXN 200002
using namespace std;
typedef long long LL;
int n,m;
bool s[MAXN],vis[MAXN];
vector<int> G[MAXN];
struct dsu{
int fa[MAXN],sz[MAXN],z[MAXN];
void init(){*fa=*sz=*z=0;for(int i=1;i<=n;i++)fa[i]=i,sz[i]=1,z[i]=0;}
int find(const int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
int size(const int x){return z[find(x)];}//当前连通块实点个数。
void add(const int x){++z[find(x)];}//连通块添加一个实点。
void merge(int x,int y){
x=find(x),y=find(y);
if(x==y)return;
if(sz[x]<sz[y]) swap(x,y);
fa[y]=x,sz[x]+=sz[y],z[x]+=z[y];
}
bool same(const int x,const int y){return find(x)==find(y);}
}S;
LL ans[MAXN];
int main(){
scanf("%d%d ",&n,&m),S.init();
for(int i=1;i<=n;i++) s[i]=getchar()^'0';
for(int i=1,u,v;i<=m;i++) scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
for(int u=1;u<=n;u++)if(s[u])for(const int v:G[u])if(s[v]) S.merge(u,v);//两个都为虚点,合并。
LL now=0;
for(int u=n;u>0;--u){
if(s[u]){
int z=S.size(u);
now-=z*(z-1ll)/2,S.add(u),now+=z*(z+1ll)/2;
}
else{
for(const int v:G[u])if((s[v]||v>u)&&!vis[S.find(v)]){
int z=S.size(v);
now-=z*(z-1ll)/2;
vis[S.find(v)]=1;//删除每个连通块原来贡献的答案,标记防止重复。
}
S.add(u);
for(const int v:G[u])if(s[v]||v>u)S.merge(u,v),vis[S.find(v)]=0;//连通。
int z=S.size(u);
now+=z*(z-1ll)/2;//更新答案。
}
ans[u]=now;
}
for(int i=1;i<=n;i++) printf("%lld\n",ans[i]);
return 0;
}

浙公网安备 33010602011771号