[LuoguP1197][JSOI2008]星球大战
Posted -wallace-
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[LuoguP1197][JSOI2008]星球大战相关的知识,希望对你有一定的参考价值。
题目意思很容易理解。
给定一个无向图,有(k)次操作,每次破坏一个点,输出每次操作后的联通块个数。
题解
一想到连通性,我们会情不自禁想到( ext{并查集})。
( ext{What!?})删点?并查集好像不支持诶。。。
但是,这题就是并查集!!!
但是思路需要小小转变一下——
[ ext{逆向思维!}]
谁说是强制在线的?
我们不妨先把图破坏成最终形态,再_将操作逆序进行_。
具体实现:
先将原始的图存下来。
设(broken[i])为此时该点是否完好。初始将所有将破坏的点置为1。
设(count)为此时连通块个数,初始(count=n-k)。
初始化并查集信息,即最终也是完好的信息。
倒着执行 加点 操作,加点时顺带(count++)。遍历该点(原始图上)的所有连边,加入并查集。记得在每次都要注意与更新(broken)。
在上述的并查集合并中,如果一开始两点不在通一个连通块中,那么将(count--)。
- 将答案在加点时记录,再用正向输出(记录是反向的)。
[ ext{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
",ans[i]);
return 0;
}
时间复杂度:(O(n+m+k+malpha +kxalpha))
(alpha) 指一次并查集操作的时间,(x)指所有被破坏的点的连边数目的平均数。
以上是关于[LuoguP1197][JSOI2008]星球大战的主要内容,如果未能解决你的问题,请参考以下文章