码迷,mamicode.com
首页 > Web开发 > 详细

[LuoguP1197][JSOI2008]星球大战

时间:2019-04-05 09:26:33      阅读:195      评论:0      收藏:0      [点我收藏+]

标签:需要   集合   use   i++   amp   hat   现在   在线的   include   

题目意思很容易理解。

给定一个无向图,有\(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\)指所有被破坏的点的连边数目的平均数。

[LuoguP1197][JSOI2008]星球大战

标签:需要   集合   use   i++   amp   hat   现在   在线的   include   

原文地址:https://www.cnblogs.com/-Wallace-/p/10657470.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!