【反向并查集、联通图】P1197 [JSOI2008]星球大战

题目描述

很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治着整个星系。

某一天,凭着一个偶然的机遇,一支反抗军摧毁了帝国的超级武器,并攻下了星系中几乎所有的星球。这些星球通过特殊的以太隧道互相直接或间接地连接。

但好景不长,很快帝国又重新造出了他的超级武器。凭借这超级武器的力量,帝国开始有计划地摧毁反抗军占领的星球。由于星球的不断被摧毁,两个星球之间的通讯通道也开始不可靠起来。

现在,反抗军首领交给你一个任务:给出原来两个星球之间的以太隧道连通情况以及帝国打击的星球顺序,以尽量快的速度求出每一次打击之后反抗军占据的星球的连通块的个数。(如果两个星球可以通过现存的以太通道直接或间接地连通,则这两个星球在同一个连通块中)。

输入输出格式

输入格式:

输入文件第一行包含两个整数,NN (1 < = N < = 2M1<=N<=2M) 和 MM (1 < = M < = 200,0001<=M<=200,000),分别表示星球的数目和以太隧道的数目。星球用 00 ~ N-1N1 的整数编号。

接下来的 MM 行,每行包括两个整数 XX, YY,其中( 0 < = X <> Y0<=X<>Y 表示星球 xx 和星球 yy 之间有 “以太” 隧道,可以直接通讯。

接下来的一行为一个整数 kk ,表示将遭受攻击的星球的数目。

接下来的 kk 行,每行有一个整数,按照顺序列出了帝国军的攻击目标。这 kk 个数互不相同,且都在 00 到 n-1n1的范围内。

输出格式:

第一行是开始时星球的连通块个数。接下来的 KK 行,每行一个整数,表示经过该次打击后现存星球的连通块个数。

样例太占地方就不放了。

这道题是在洛谷刷并查集时遇到的,所以看到的时候想了一下就知道是反向并查集了。

简单的建图保存边,还有并查集的基本操作。需要注意的是计算出联通块的数量后,在结果上要减去已经爆炸的星星数量(爆炸后是孤立的点);

以及在从后往前计算的过程中,没计算完一颗星星,要记得把Is_Lose[]的值刷新,因为计算前i 次爆炸时,后面的星星依然存在。

剩下的就是些基本操作了,做完才发现是道水题。。。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int Max_E= 500005;
const int Max_V= 500005;
struct ENode
{
    int to;
    int Next;
};
ENode Edegs[Max_E];
int Head[Max_V];
int tnt= -1;
void Add_ENode (int u, int v)
{
    ++ tnt;
    Edegs[tnt].to= v;
    Edegs[tnt].Next= Head[u];
    Head[u]= tnt;
}
int pre[Max_V];
void into(int N)
{
    for (int i= 0; i<= N; i ++)
    {
        pre[i]= i;
        Head[i]= -1;
    }
}
int find_F (int x)
{
    int r= x;
    while (r!= pre[r])
    {
        r= pre[r];
    }
    int i= x, j;
    while (i!= r)
    {
        j= pre[i];
        pre[i]= r;
        i= j;
    }
    return r;
}
void join (int x, int y)
{
    int a= find_F(x);
    int b= find_F(y);
    if (a!= b) pre[a]= b;
}
int It_Lose[Max_V]; //按顺序依次记录被炸掉的行星
bool Is_Lose[Max_V];//标记行星此刻是否被炸毁
int Is_Many[Max_V]; //记录第Ki时联通块的个数
int pre1[Max_V];    // copy 一下 pre[];
int main()
{
    int n, m;
    int a, b;
    cin >>n >>m;
    into(n);
    tnt= -1;
    while (m --)
    {
        cin >>a >>b;
        Add_ENode(a, b);
        Add_ENode(b, a);
    }
    int k, c;
    memset(Is_Lose, false, sizeof(Is_Lose));
    cin >>k;
    for (int j= 1; j<= k; j ++)
    {
        cin >>c;
        It_Lose[j]= c;
        Is_Lose[c]= true;
    }
    for (int i= 0; i< n; i ++)
    {
        if (Is_Lose[i]) continue;
        for (int j= Head[i]; j!= -1; j= Edegs[j].Next)
        {
            int v= Edegs[j].to;
            if (! Is_Lose[v]) join(v, i);
        }
    }
    /*copy 一下pre[], 然后对pre1[]排序*/
    for (int i= 0; i< n; i ++) pre1[i]= find_F(i);
    sort(pre1, pre1+ n);
    int cnt= 1, ans;
    /*遍历排序后的pre1[],计算出有多少个根节点不同的点,
    即几个块联通块*/
    for (int i= 0; i< n; i ++)
    {
        if (! i)
        {
            ans= pre1[i];
            continue;
        }
        if (pre1[i]!= ans) ans= pre1[i], ++ cnt;
    }
    /*最后一次,即第k 次爆炸后剩余的不联通的星系数量,
    等于此时总联通块的数量减去已经爆炸的星星,即减k */
    Is_Many[k]= cnt- k;

    /*从后往前遍历炸毁的星星,开始添点接边,反向并查集*/
    for (int i= k- 1; i>= 0; i --)
    {
        /*第i 颗星星爆炸后的联通块数量,
        即加上第i+ 1颗星星后的联通块数量*/
        int u= It_Lose[i+ 1];
        Is_Lose[u]= 0;   //在这次计算时,u还未被炸毁
        for (int j= Head[u]; j!= -1; j= Edegs[j].Next)
        {
            /*遍历第i+ 1颗星星的所有边,接边。*/
            int v= Edegs[j].to;
            if (Is_Lose[v]) continue;
            /*若边连接的终点未被炸毁,
            判断终点与起点的根节点是否相同,若不相同,
            证明两个本不联通的块被联通,计数器cnt减1*/
            if (find_F(v)!= find_F(u)) join(v, u), --cnt;
        }
        Is_Many[i]= cnt- i;  //记得减去已被炸毁的星星
    }
    /*输出结果w*/
    for (int i= 0; i<= k; i ++)
    {
        cout << Is_Many[i] << endl;
    }
    return 0;
}

end;


原文地址:https://www.cnblogs.com/Amaris-diana/p/10620801.html