P1197 [JSOI2008]星球大战
算法
并查集+逆序
思路
做这道题前呢,我们先出门左转关闭农场,一道类似的更简单一丢丢的题
然后,我们考虑一下这题,因为并没有过多的操作,只是要我们求一下连通块的个数而已(也就是连通性,具有传递性的连通),而这恰好是并查集所擅长的。
然而,我们正向看题目时就会发现不支持删除操作的并查集似乎办不到。但,如果我们把删除操作换成加入操作,就可以办到了。这也就是逆向思维。题目支持离线,所以我们可以逆向处理出所有答案以后再全部输出就好了。
考虑最后的连通状态:标记摧毁的点,把不摧毁的点之间能连的边连起来,同时统计连通块的数量即为所求
中间的连通状态:于最后的连通状态处理方法类似,从后往前依次加入被删除的点,枚举出边,连通。统计连通块的数量即为所求
倒序存,正序输shu
#include<bits/stdc++.h> using namespace std; int now[400010],tot; int team[400010]; bool flag[400010];//是否存在 int father[400010]; int n,m,k; int ans[400010]; int sum; struct node { int from; int to; int next; }a[400010];//邻接表存图 void put(int x,int y) { a[tot].from=x; a[tot].next=now[x]; a[tot].to=y; now[x]=tot; tot++; } int find(int x) { if(x!=father[x])father[x]=find(father[x]); return father[x]; }//并查集基本操作 void unionn(int x,int y) { x=find(x),y=find(y); if(x==y)return; sum--;//合并集合减一 father[x]=y; }//同上 int main() { scanf("%d%d",&n,&m); int x,y; for(int i=0;i<n;i++) father[i]=i,now[i]=-1;//初始化 for(int i=1;i<=m;i++) { scanf("%d%d",&x,&y); put(x,y); put(y,x);//存图 } scanf("%d",&k); sum=n-k;//假设剩下的都不联通 for(int i=1;i<=k;i++) { scanf("%d",&x); flag[x]=true; team[i]=x; } for(int i=0;i<2*m;i++) { if(!flag[a[i].from]&&!flag[a[i].to]) { unionn(a[i].from,a[i].to);//链接所有边 } } //printf("k:%d\n",k); ans[k+1]=sum; for(int i=k;i>=1;i--) { int u=team[i]; sum++;//新增一个点+1连通块 flag[u]=false; for(int i=now[u];i!=-1;i=a[i].next) { if(!flag[a[i].from]&&!flag[a[i].to])//都在并查集内(没被摧毁) { unionn(a[i].from,a[i].to); } } ans[i]=sum; }//倒着做 // printf("k:%d\n",k); for(int i=1;i<=k+1;i++) printf("%d\n",ans[i]); }
细雨斜风作晓寒,
淡烟疏柳媚晴滩。
入淮清洛渐漫漫。
雪沫乳花浮午盏,
蓼茸蒿笋试春盘。
人间有味是清欢。