题解 P1197 【[JSOI2008]星球大战】
思路:
本题目与 \(p2700\) 类似。
向图中正向减点减边,每次都构建并查集,计算联通块的方法
肯定会TLE
则需要把思路调转:
需要先将路径都推倒,再重新建边
先输入各边,被摧毁的星球,
并将其离线储存 (注意存无向图)
之后,建起不包括存储的 被摧毁的星球 的并查集,
这样就得到了最后状态的联通块情况与数量
之后按从后往前的被摧毁顺序,
依次将被摧毁的的星球复原,
添加到并查集中。
并将这个被摧毁星球
与其他当前没有被摧毁的星球
按照储存的路径相连接
就可以分别得到
复原某个星球时的联通块数
当复原所有被摧毁星球后,
就得到了原始状态的联通块数
再按照处理顺序,
反向输出联通块数,即可;
ps:此处join函数不只有并集的作用,还用来计算联通块数
附上 奇丑的 AC代码:
#include<cstdio>
using namespace std;
int n,m,k;
int pre[400010],kk[400010],head[400010],ans[400010];
//分存祖先,被摧毁星球编号,邻接表各点所对的边,答案
struct baka9
{
int u,v,ne;//存边
}bian[400010];//无向图,两倍边
int lian,num;//存联通块数与边数
bool judge[400010];//判断星球是否爆炸
int find(int x);//查集
void join(int x,int y);//并集并计算联通块数
void add(int x,int y);//添加边
//---------------------------------------------------
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
pre[i]=i;//初始化祖先
for(int i=1;i<=m;i++)//输入各边
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y);//添加无向图
add(y,x);
}
scanf("%d",&k);
for(int i=k;i>=1;i--)//反向记录被摧毁星球
{
scanf("%d",&kk[i]);
judge[kk[i]]=1;
}
lian=n-k;//初始化联通块数,使每个没被摧毁的星球都单独在一个集子里
for(int i=0;i<n;i++)//构建最后位置的并查集
{
if(judge[i])//被摧毁了,就找下一个
continue;
for(int j=head[i];j;j=bian[j].ne)//没被摧毁,就把它与相邻的没摧毁的星球添加到并查集中
if(judge[bian[j].v] == 0)
join(bian[j].u,bian[j].v);
}
ans[k+1]=lian;//计算出了最后位置的联通块数
for(int i=1;i<=k;i++)//计算各位置的联通块
{
judge[kk[i]]=0;//还原星球
lian++;//初始化,使这个星球单独一个集.
for(int j=head[kk[i]];j;j=bian[j].ne)//添加没爆炸的相邻星球
if(judge[bian[j].v] == 0)
join(bian[j].u,bian[j].v);
ans[k-i+1]=lian;//记录
}
for(int i=1;i<=k+1;i++)//输出
printf("%d\n",ans[i]);
return 0;//完美潇洒の结束
}
//---------------------------------------------------
int find(int x)//查集
{
if(pre[x]==x) return x;
else return pre[x]=find(pre[x]);
}
void join(int x,int y)//并集
{
int r1=find(x);
int r2=find(y);
if(r1 != r2)
{
pre[r2]=r1;//如果有两个不同组的集合合并
lian--;//那么联通块数减一.
}
}
void add(int x,int y)//邻接表加边
{
bian[++num].ne=head[x];
bian[num].u=x;
bian[num].v=y;
head[x]=num;
}
作者@Luckyblock,转载请声明出处。