[LuoguP1197][JSOI2008]星球大战
作者:互联网
题目意思很容易理解。
给定一个无向图,有\(k\)次操作,每次破坏一个点,输出每次操作后的联通块个数。
题解
一想到连通性,我们会情不自禁想到\(\text{并查集}\)。
\(\text{What!?}\)删点?并查集好像不支持诶。。。
但是,这题就是并查集!!!
但是思路需要小小转变一下——
\[\text{逆向思维!}\]
谁说是强制在线的?
我们不妨先把图破坏成最终形态,再_将操作逆序进行_。
具体实现:
先将原始的图存下来。
设\(broken[i]\)为此时该点是否完好。初始将所有将破坏的点置为1。
设\(count\)为此时连通块个数,初始\(count=n-k\)。
初始化并查集信息,即最终也是完好的信息。
倒着执行 加点 操作,加点时顺带\(count++\)。遍历该点(原始图上)的所有连边,加入并查集。记得在每次都要注意与更新\(broken\)。
在上述的并查集合并中,如果一开始两点不在通一个连通块中,那么将\(count--\)。
- 将答案在加点时记录,再用正向输出(记录是反向的)。
\[\text{MainCodeHere}\]
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
const int MAX_N=1e6+10;
int uset[MAX_N];
vector<int> G[MAX_N];
bool broken[MAX_N];
int opts[MAX_N];
int ans[MAX_N];
int n,m,k;
int count;
int find(int x){
return (x==uset[x])?x:uset[x]=find(uset[x]);
//常规的并查集查找。
}
inline void merge(int x,int y){
int fx=find(x),fy=find(y);
if(fx==fy)return;//如果本来就是同一个,忽略。
count--;uset[fx]=fy;
//不在同一个连通块,合并之后家记得将count--!
}
int main()
{
register int i,j;
scanf("%d%d",&n,&m);
memset(broken,0,sizeof(broken));
for(i=0;i<n;i++)uset[i]=i;
for(i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
G[u].push_back(v) ;
G[v].push_back(u) ;
//建立原始图
}
scanf("%d",&k);
count=n-k;//最初并查集的连通块个数。
for(i=1;i<=k;i++)
scanf("%d",&opts[i]),broken[opts[i]]=1;//记录最终会被破坏的点。
for(i=0;i<n;i++)
{
if(broken[i])continue;
for(j=0;j<G[i].size() ;j++)
{
if(broken[G[i][j]])continue;
merge(i,G[i][j]);
}
//在并查集上初始化永久完好的信息。
}//O(m*UFS)
for(i=k;i>=1;i--)//逆序
{
ans[i]=count++;
//记录答案,注意加点时连通块要先+1(毕竟多了一个点)。
broken[opts[i]]=0;//现在它就不是损坏了的了。
for(j=0;j<G[opts[i]].size() ;j++)
if(!broken[G[opts[i]][j]])//注意不要将此时还没有加入的点合并如并查集。
merge(opts[i],G[opts[i]][j]);
}//O(k*x*UFS)
ans[0]=count;
//注意题目还要求记录完好时连通块的个数。
for(i=0;i<=k;i++)//正序输出
printf("%d\n",ans[i]);
return 0;
}
时间复杂度:\(O(n+m+k+m\alpha +kx\alpha)\)
\(\alpha\) 指一次并查集操作的时间,\(x\)指所有被破坏的点的连边数目的平均数。
标签:count,JSOI2008,int,MAX,查集,LuoguP1197,broken,uset,星球大战 来源: https://www.cnblogs.com/-Wallace-/p/10657470.html