題解 P1197 【[JSOI2008]星球大戰】
阿新 • • 發佈:2018-12-02
主要思路:逆向思維
看到題目,第一個感覺,,,
連通塊???
我剛學過的搜尋呢???深搜廣搜都可以啊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;
}