专栏文章

题解 P11674

P11674题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@miqbyazw
此快照首次捕获于
2025/12/04 02:18
3 个月前
此快照最后确认于
2025/12/04 02:18
3 个月前
查看原文
第一道场切的 Gold,希望不是最后一道。

题意:

无向图上两类操作:00 直接删点; 11 删点但保留互相连边。问每次操作结束后连通的点对数量。

思路:

看到无向图以及删除类操作,Recalling 同为金组的 P8097,考虑反向操作。
首先,不难发现,所有删点操作为 00 的点,直接遍历它的连边即可更新结果。但 11 类节点删除后的添边有后效性,似乎不太好处理。
接着,思考操作 11 后效的性质,我们发现所有连通的 11 类点,以及与它们有连边的其它 00 点,可以组成一个 Cluster。这些点之间随时都保证互有连边(只要点存在)。
于是,每个点的不同类型已经无关紧要了。添加一个点时,直接找它当前所在的 Cluster 是否已经有点被添加了进来。如果有,直接将它们连通并计算答案。否则,按照 00 类点的方式处理即可。
复杂度瓶颈在于并查集(和一个没啥卵用的 Cluster 内部排序)。懒得优化直接路径压缩就带一个 log,当然不影响过题。

程序如下:

CPP
#include<cstdio>
#include<vector>
#include<algorithm>
#include<set>
#include<cstring>
using namespace std;
const int N=4e5+5;
long long n,m,fa[N],sz[N];
bool vis[N];
char s[N];
long long ans[N];
vector<int>g[N],clu[N],inclu[N];
int find(int x){
	if(fa[x]==x)return x;
	else return fa[x]=find(fa[x]);
}
void dfs(int ori,int u){
	if(vis[u])return;
	clu[ori].push_back(u);
	if(s[u]!='1')return;
	vis[u]=true;
	for(auto v:g[u])dfs(ori,v);
}
int main(){
	scanf("%d%d%s",&n,&m,s+1);
	for(int i=1;i<=m;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	for(int i=1;i<=n;i++)
		if(s[i]=='1'&&!vis[i]){
			dfs(i,i);
			sort(clu[i].begin(),clu[i].end());
			for(auto cur:clu[i])inclu[cur].push_back(i);
		}
	for(int i=1;i<=n;i++)fa[i]=i,sz[i]=1;
	for(int i=n;i>=1;i--){
		ans[i]=ans[i+1];
		for(auto cur:inclu[i]){
			int nth=clu[cur].size()-1;
			if(clu[cur][nth]>i){
				int un1=find(clu[cur][nth]),un2=find(i);
				if(un1==un2)continue;
				ans[i]+=1ll*sz[un1]*sz[un2];
				fa[un2]=un1;
				sz[un1]+=sz[un2];
			}
		}
		for(auto v:g[i]){
			if(v<i)continue;
			int un1=find(v),un2=find(i);
			if(un1==un2)continue;
			ans[i]+=1ll*sz[un1]*sz[un2];
			fa[un2]=un1;
			sz[un1]+=sz[un2];
		}
	}
	for(int i=1;i<=n;i++)printf("%lld\n",ans[i]);
	return 0;
}

THE END

评论

0 条评论,欢迎与作者交流。

正在加载评论...