1. 程式人生 > >BZOJ1015或洛谷1197 [JSOI2008]星球大戰

BZOJ1015或洛谷1197 [JSOI2008]星球大戰

BZOJ原題連結

洛谷原題連結

發現正著想毫無思路,所以我們可以考慮倒著思考,把摧毀變成建造。
這樣很容易想到用並查集來維護連通塊,問題也變的很簡單了。
建原圖,先遍歷一遍所有邊,若某條邊的兩端點未被摧毀,那麼合併兩個點,再倒著去列舉被摧毀的點,對於一個點遍歷它的邊,若是未摧毀的點,那麼就用並查集將兩個連通塊合併,並記錄答案即可。

#include<cstdio>
using namespace std;
const int N = 4e5 + 10;
struct eg {
    int x, y;
};
eg a[N];
int fi[N], di[N], ne[N], fa[N], an[N], br[N], l;
bool v[N];
inline int re()
{
    int x = 0;
    char c = getchar();
    bool p = 0;
    for (; c < '0' || c > '9'; c = getchar())
        p |= c == '-';
    for (; c >= '0' && c <= '9'; c = getchar())
        x = x * 10 + c - '0';
    return p ? -x : x;
}
inline void add(int x, int y)
{
    di[++l] = y;
    ne[l] = fi[x];
    fi[x] = l;
}
int fin(int x)
{
    if (!(fa[x] ^ x))
        return x;
    return fa[x] = fin(fa[x]);
}
int main()
{
    int i, j, n, m, x, y, k, s;
    n = re();
    m = re();
    for (i = 1; i <= m; i++)
    {
        a[i].x = re();
        a[i].y = re();
        add(a[i].x, a[i].y);
        add(a[i].y, a[i].x);
    }
    for (i = 1; i <= n; i++)
        fa[i] = i;
    k = re();
    for (i = 1; i <= k; i++)
    {
        br[i] = re();
        v[br[i]] = 1;
    }
    s = n - k;
    for (i = 1; i <= m; i++)
        if (!v[a[i].x] && !v[a[i].y])
        {
            x = fin(a[i].x);
            y = fin(a[i].y);
            if (x ^ y)
            {
                fa[x] = y;
                s--;
            }
        }
    an[k] = s;
    for (i = k; i; i--)
    {
        v[x = br[i]] = 0;
        s++;
        for (j = fi[x]; j; j = ne[j])
            if (!v[y = di[j]])
            {
                x = fin(x);
                y = fin(y);
                if (x ^ y)
                {
                    fa[x] = y;
                    s--;
                }
            }
        an[i - 1] = s;
    }
    for (i = 0; i <= k; i++)
        printf("%d\n", an[i]);
    return 0;
}