黑魔法师之门 (magician)-并查集
题目
经过了 16 个工作日的紧张忙碌,未来的人类终于收集到了足够的能源。然而在与 Violet 星球的战争中,由于 Z 副官的愚蠢,地球的领袖 applepi 被邪恶的黑魔法师 Vani 囚禁在了 Violet 星球。为了重启 Nescafé这一宏伟的科技工程,人类派出了一支由 XLk、Poet_shy 和 lydrainbowcat 三人组成的精英队伍,穿越时空隧道,去往 Violet 星球拯救领袖 applepi。 applepi 被囚禁的地点只有一扇门,当地人称它为“黑魔法师之门”。这扇门上画着一张无向无权图,而打开这扇门的密码就是图中每个点的度数大于零且都是偶数的子图的个数对1000000009 取模的值。此处子图 (V, E) 定义为:点集 V和边集 E 都是原图的任意子集,其中 E 中的边的端点都在V中。但是 Vani 认为这样的密码过于简单,因此门上的图是动态的。起初图中只有 N 个顶点而没有边。Vani 建造的门控系统共操作 M 次,每次往图中添加一条边。你必须在每次操作后都填写正确的密码,才能够打开黑魔法师的牢狱,去拯救伟大的领袖 applepi。
输入输出
第一行包含两个整数 N 和 M。 接下来 M 行,每行两个整数 A和 B,代表门控系统添加了一条无向边 (A, B)。
输出一共 M 行,表示每次操作后的密码。
输入样例
4 8 3 1 3 2 2 1 2 1 1 3 1 4 2 4 2 3
输出样例
0 0 1 3 7 7 15 31
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<fstream> #include<set> #define ll long long using namespace std; int read(){ int res=0,f=1; char ch; ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-')f=-1; ch=getchar(); } while(ch>='0'&&ch<='9'){ res=res*10+(ch-'0'); ch=getchar(); } return res*f; } const int MAXN=200005; const ll mod=1000000009; int n,m; set<int> u[MAXN]; ll ans; int main(){ n=read();m=read(); for(int i=1;i<=n;++i)u[i].insert(i); for(int i=1;i<=m;++i){ int c,b; b=read(); c=read(); int fl=0; for(set<int>::iterator iter=u[b].begin();iter!=u[b].end();++iter){ int p=*iter; if(u[c].count(p)){ fl=1; break; } } u[b].insert(c);u[c].insert(b); if(fl)ans=ans*2+1; printf("%lld\n",ans); } fclose(stdin); fclose(stdout); return 0; }
不过这段代码的速度与正确性都难以保证,毕竟一个环也是满足提议的,也就是说还要将能找到的更新一遍。绝对超时。
于是,就有了我们的并查集。(并查集是什么我就不写了)
运用并查集,若两端在同一集合中,就*2+1,若不在,就加入到同一集合,不改变答案。
ACcode:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<fstream> #include<set> #define ll long long using namespace std; int read(){ int res=0,f=1; char ch; ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-')f=-1; ch=getchar(); } while(ch>='0'&&ch<='9'){ res=res*10+(ch-'0'); ch=getchar(); } return res*f; } const int MAXN=200005; const ll mod=1000000009; int n,m; int f[MAXN]; ll ans; int find_f(int x){ return x==f[x]?x:f[x]=find_f(f[x]); } int main(){ n=read();m=read(); for(int i=1;i<=n;++i)f[i]=i; for(int i=1;i<=m;++i){ int c,b; b=read(); c=read(); int q=find_f(b),p=find_f(c); if(q!=p){ f[q]=c; } else ans=(ans*2+1)%mod; printf("%lld\n",ans); } fclose(stdin); fclose(stdout); return 0; }
不过,笔者我到现在也不太能够证明这个方法的正确性。我唯一能想到的解释就是,若在同一集合,则必然找的到一条路径使得两端点联通,此时这条路径与新加入的边是等价的。那么此时新加入的边与路径构成一个只有两个点的圆,是一个全新的子图,这是+1.而新加入的边同时也可以代替那一条路径,使得原来的方案双倍。这是*2.