主要思路:逆向思维
看到题目,第一个感觉,,,
连通块???
我刚学过的搜索呢???深搜广搜都可以啊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; }