1. 程式人生 > >題解 P1197 【[JSOI2008]星球大戰】

題解 P1197 【[JSOI2008]星球大戰】

主要思路:逆向思維

看到題目,第一個感覺,,,

連通塊???

我剛學過的搜尋呢???深搜廣搜都可以啊QwQ!

但很多人都被困在了這個攻佔星球(也就是去點)上。

如果再仔細看下題目,發現可以離線做這道題。

那麼方法來了:

我們是不是可以把所有的邊存下來,把被攻佔的星球的順序存下來,先把所有兩端都沒有被攻佔的邊加上,先求一遍連通塊個數。然後反向的加點加邊,邊加邊邊求連通塊個數,把答案反向存下來,然後正向輸出。

這題結束了(偽)。於是程式碼如下:

程式碼1(20分):

程式碼解釋在最後的程式碼中
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
using namespace std;
#define go(i, j, n, k) for (int i = j; i <= n; i += k)
#define fo(i, j, n, k) for (int i = j; i >= n; i -= k)
#define rep(i, x) for (int i = h[x]; i; i = e[i].nxt)
#define mn 400400
#define inf 1 << 30
#define ll long long
#define ld long double
#define fi first
#define se second
#define root 1, n, 1
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
#define bson l, r, rt
inline int read(){
    int f = 1, x = 0;char ch = getchar();
    while (ch > '9' || ch < '0'){if (ch == '-')f = -f;ch = getchar();}
    while (ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
    return x * f;
}
inline void write(int x){
    if (x < 0)putchar('-'),x = -x;
    if (x > 9)write(x / 10);
    putchar(x % 10 + '0');
}
//This is AC head above...
int ans[mn], n, m, k;
struct edge{
    int v,nxt/*,w*/;
} e[mn<<1];
int h[mn],p;
inline void add(int a,int b/*,int c*/){
    p++;
    e[p].nxt=h[a];
    h[a]=p;
    e[p].v=b;
    //e[p].w=c;
}
int usd[mn];
pair<int, int> ee[mn];
bool not_alive[mn], vis[mn], not_in[mn];
void dfs(int x){
    if(vis[x] && !not_alive[x])
        return;
    vis[x] = true;
    rep(i,x){
        if(!vis[e[i].v] && !not_alive[e[i].v])
            dfs(e[i].v);
    }
}
int main(){
    n = read();
    m = read();
    go(i,1,m,1){
        ee[i].first = read();
        ee[i].second = read();
    }
    k = read();
    memset(not_alive, false, sizeof(not_alive));
    fo(i,k,1,1){
        usd[i] = read();
        not_alive[usd[i]] = true;
    }
    //cout << "\n";
    go(i, 1, m, 1){
        if (!not_alive[ee[i].first] && !not_alive[ee[i].second]){
            add(ee[i].first, ee[i].second);
            add(ee[i].second, ee[i].first);
            //cout << "111111111111111" << "\n";
            //cout << ee[i].first << " " << ee[i].second << "\n";
        }else{
            not_in[i] = true;
        }
    }

    int _ans = 0;

    memset(vis, false, sizeof(vis));
    _ans = 0;
    go(i,0,n-1,1){
        if(!vis[i] && !not_alive[i]){
            dfs(i);
            _ans++;
        }
    }
    ans[k + 1] = _ans;

    go(i,1,k,1){
        not_alive[usd[i]] = false;
        go(i,1,m,1){
            if(not_in[i]){
                if(!not_alive[ee[i].first] && !not_alive[ee[i].second]){
                    add(ee[i].first, ee[i].second);
                    add(ee[i].second, ee[i].first);
                    //cout << i << "\n";
                }
            }
        }
        memset(vis, false, sizeof(vis));
        _ans = 0;
        go(i,0,n-1,1){
            if(!vis[i] && !not_alive[i]){
                dfs(i);
                _ans++;
                //cout << i << " ";
            }
        }
        ans[k - i + 1] = _ans;
        //cout << "\n";
    }
/*
    memset(vis, false, sizeof(vis));
    _ans = 0;
    go(i,0,n-1,1){
        if(!vis[i] && !not_alive[i]){
            dfs(i);
            _ans++;
        }
    }
    cout << "\n\n" << _ans;
*/
    //cout << "\n";
    go(i,1,k+1,1){
        cout << ans[i] << "\n";
    }
    return 0;
}

20分???發生了什麼???

TLE怎麼辦??還有個RE QAQ

等等,RE是怎麼回事?

是不是棧空間用的太多了??dfs會佔用一些棧空間。

這時我們會想到:並查集

如果把dfs找連通塊改用並查集找連通塊,會省下一些棧空間和一些添邊的時間

於是,我們有了:

程式碼2(30分)

程式碼解釋在最後的程式碼中
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
using namespace std;
#define go(i, j, n, k) for (int i = j; i <= n; i += k)
#define fo(i, j, n, k) for (int i = j; i >= n; i -= k)
#define rep(i, x) for (int i = h[x]; i; i = e[i].nxt)
#define mn 400400
#define inf 1 << 30
#define ll long long
#define ld long double
#define fi first
#define se second
#define root 1, n, 1
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
#define bson l, r, rt
inline int read(){
    int f = 1, x = 0;char ch = getchar();
    while (ch > '9' || ch < '0'){if (ch == '-')f = -f;ch = getchar();}
    while (ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
    return x * f;
}
inline void write(int x){
    if (x < 0)putchar('-'),x = -x;
    if (x > 9)write(x / 10);
    putchar(x % 10 + '0');
}
//This is AC head above...
int n, m, k, usd[mn], ans[mn];
pair<int, int> ee[mn];
bool not_alive[mn];
struct edge{
    int v,nxt/*,w*/;
} e[mn<<1];
int h[mn],p;
inline void add(int a,int b/*,int c*/){
    p++;
    e[p].nxt=h[a];
    h[a]=p;
    e[p].v=b;
    //e[p].w=c;
}
int father[mn];
inline int findx(int x){
    return father[x] == x ? x : father[x] = findx(father[x]);
}
inline void mergex(int x,int y){
    int xx = findx(x);
    int yy = findx(y);
    if (xx == yy)
        return;
    srand((unsigned)time(NULL));
    if(rand()%2){
        father[xx] = yy;
    }else{
        father[yy] = xx;
    }
}
int main(){
    n = read();
    go(i,1,n,1){
        father[i] = i;
    }
    m = read();
    go(i,1,m,1){
        ee[i].first = read();
        ee[i].second = read();
        add(ee[i].first, ee[i].second);
        add(ee[i].second, ee[i].first);
    }
    k = read();
    memset(not_alive, false, sizeof(not_alive));
    fo(i,k,1,1){
        usd[i] = read();
        not_alive[usd[i]] = true;
    }
    int tot = n - k;
    go(i,1,m,1){
        if(!not_alive[ee[i].first] && !not_alive[ee[i].second]
        && findx(ee[i].first) != findx(ee[i].second)){
            tot--;
            mergex(ee[i].first, ee[i].second);
        }
    }
    ans[k + 1] = tot;
    go(i,1,k,1){
        tot++;
        not_alive[usd[i]] = false;
        go(i,1,m,1){
            if(!not_alive[ee[i].first] && !not_alive[ee[i].second]
            && findx(ee[i].first) != findx(ee[i].second)){
                tot--;
                mergex(ee[i].first, ee[i].second);
            }
        }
        ans[k - i + 1] = tot;
    }
    go(i,1,k+1,1){
        cout << ans[i] << "\n";
    }
    return 0;
}

很好,RE沒有了,TLE沒有解決。

我們簡單的分析一下可以發現,程式碼的複雜度是O(k*(n+m))的,,,WA!好大

我們分析一下我們哪個地方費的時間多。

對,在添點與找連通塊數量上。如何優化這個地方?

我們可以發現,如果一個圖n個點沒有邊,會有幾個連通塊??

是不是有n個?

如果我們在其中兩個點中加一條邊的話,是不是連通塊數量為n-1個?

那麼我每當一條邊加進去時,多了一個點加入一個連通塊,那麼總的連通塊數就會-1?

這樣的話,我們就可以先算出加點之前的連通塊數,然後每加一個點,先把連通塊數+1,然後看這個點是否可以連入其他連通塊中(注:這裡只需要把與這個點連線的邊枚舉出來就可以了),如果可以,連通塊數-1,在每次操作後倒序記錄連通塊數,最後正序輸出就好啦!

於是,我們有了——

程式碼3(再不AC這個題解就完了):

 #include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
using namespace std;
#define go(i, j, n, k) for (int i = j; i <= n; i += k)
#define fo(i, j, n, k) for (int i = j; i >= n; i -= k)
#define rep(i, x) for (int i = h[x]; i; i = e[i].nxt)
#define mn 400400
#define inf 1 << 30
#define ll long long
#define ld long double
#define fi first
#define se second
#define root 1, n, 1
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
#define bson l, r, rt
inline int read(){
    int f = 1, x = 0;char ch = getchar();
    while (ch > '9' || ch < '0'){if (ch == '-')f = -f;ch = getchar();}
    while (ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
    return x * f;
}
inline void write(int x){
    if (x < 0)putchar('-'),x = -x;
    if (x > 9)write(x / 10);
    putchar(x % 10 + '0');
}
//This is AC head above...
int n, m, k, usd[mn], ans[mn];
pair<int, int> ee[mn];
bool not_alive[mn];
struct edge{
    int v,nxt/*,w*/;
} e[mn<<1];
int h[mn],p;
inline void add(int a,int b/*,int c*/){
    p++;
    e[p].nxt=h[a];
    h[a]=p;
    e[p].v=b;
    //e[p].w=c;
}
//鏈式前向星存圖
int father[mn];
inline int findx(int x){
    return father[x] == x ? x : father[x] = findx(father[x]);
}
inline void mergex(int x,int y){
    int xx = findx(x);
    int yy = findx(y);
    if (xx == yy)
        return;
    srand((unsigned)time(NULL));//隨機合併防止毒瘤出題人故意卡深度(自己都不知道會怎麼並)
    if(rand()%2){
        father[xx] = yy;
    }else{
        father[yy] = xx;
    }
}
//並查集
int main(){
    n = read();
    go(i,1,n,1){
        father[i] = i;
    }
    m = read();
    go(i,1,m,1){
        ee[i].first = read();//離線判斷用
        ee[i].second = read();
        add(ee[i].first, ee[i].second);//存圖
        add(ee[i].second, ee[i].first);
    }
    k = read();
    memset(not_alive, false, sizeof(not_alive));//玄學初始化
    fo(i,k,1,1){
        usd[i] = read();//倒序記錄被炸的順序
        not_alive[usd[i]] = true;//記錄哪個點 最後被炸掉了
    }
    int tot = n - k;//重點!這裡記錄目前剩的點數
    go(i,1,m,1){//然後先把存活的點之間的邊連上,放到一個集合裡,總的連通塊數-1
        if(!not_alive[ee[i].first] && !not_alive[ee[i].second]
        && findx(ee[i].first) != findx(ee[i].second)){
            tot--;
            mergex(ee[i].first, ee[i].second);
        }
    }
    ans[k + 1] = tot;
    go(i,1,k,1){
        tot++;
        not_alive[usd[i]] = false;
        for (int j = h[usd[i]]; j; j = e[j].nxt){//列舉與這個點連線的點,看會合並幾次,合併幾次就會減少幾個連通塊
            if(!not_alive[e[j].v] && findx(e[j].v) != findx(usd[i])){
                tot--;
                mergex(e[j].v, usd[i]);
            }
        }
        ans[k - i + 1] = tot;//倒序儲存
    }
    go(i,1,k+1,1){//正序輸出
        cout << ans[i] << "\n";
    }
    return 0;
}

第八次發題解,希望可以幫到那些不知道怎麼去點怎麼刪邊的同學