【思维题 集合hash 树上差分】11.5撸树
要注重问题的转化和一些结论的推断
题目描述
要致富,先撸树。
一棵树的形状可以简化为一张 $N$ 个点 $M$ 条边的图,由于装备条件限制,你只有撸两次,也就是删去两条边,当这张图不联通时,就意味着树倒了。
现在你想知道有多少种方案能撸倒这棵树。
输入格式
第一行两个正整数 $n,m$
接下来 $m$ 行,每行两个正整数,表示一条边。
输出格式
输出一个数,表示方案数。
数据规模与约定
对于 $30\%$ 的数据,$1\le N\le 20,1\le M\le40$
对于$50\%$的数据,$1\le N\le500,1\le M\le1000$
对于$100\%$的数据,$1\le N\le50000,1\le M\le100000$
保证刚开始图是联通的。
时间限制:1s
空间限制:512M
题目分析
题外话
有重边
好好分析一下
图的问题先转化为树,于是先预处理出树边与非树边,再来考虑非树边对于树的影响。
首先是一些定义:对每一条边,如果是树边就使用哈希记录下经过它的非树边,并将这个哈希值称作“经过哈希值”、边的数量称作“经过数”;如果是非树边,“经过哈希值”就是它的哈希值本身、“经过数”不计。
然后是结论:若一条边(树边)的经过数为0(也就意味着它是一条割边),说明选了它再任选一条边都可行;若两条边的经过哈希值相同,意味着必须同时取两条这种边才能将图分为两半。
接下去考虑算法实现。
注意到这里哈希是集合哈希,那么一种经典方法就是rand一个大数(long long),再异或起来。对于一条非树边$(u,v)$,我们需要把树上路径$u->v$这一段都加上标记,因此考虑树上差分。这里有一个小技巧,因为此处操作是异或,而两端点同时异或一个相同的数,那么就不用像常规那样求出lca并除去贡献,只需要在打完标记之后再从上至下dfs一遍记录每一条边的哈希值。
还有一个处理的技巧:将哈希开成unsigned long long;双哈希开std::pair。如此一来排序时候就自然而然将经过数为0的边排在前面,天然保证了答案的顺序。
1 #include<bits/stdc++.h> 2 typedef unsigned long long ll; 3 typedef std::pair<ll, ll> pr; 4 const int maxn = 50035; 5 const int maxm = 200035; 6 7 struct Edge 8 { 9 int v,id; 10 Edge(int a=0, int b=0):v(a),id(b) {} 11 }edges[maxm]; 12 int n,m,u[maxm],v[maxm],fa[maxn]; 13 int edgeTot,head[maxn],nxt[maxm]; 14 pr eval[maxm],ev[maxn]; 15 ll ans; 16 17 int read() 18 { 19 char ch = getchar(); 20 int num = 0; 21 bool fl = 0; 22 for (; !isdigit(ch); ch=getchar()) 23 if (ch=='-') fl = 1; 24 for (; isdigit(ch); ch=getchar()) 25 num = (num<<1)+(num<<3)+ch-48; 26 if (fl) num = -num; 27 return num; 28 } 29 pr operator ^(pr a, pr b){return pr(a.first^b.first, a.second^b.second);} 30 int get(int x){return x==fa[x]?x:fa[x]=get(fa[x]);} 31 void addedge(int u, int v, int id) 32 { 33 edges[++edgeTot] = Edge(v, id), nxt[edgeTot] = head[u], head[u] = edgeTot; 34 edges[++edgeTot] = Edge(u, id), nxt[edgeTot] = head[v], head[v] = edgeTot; 35 } 36 void count(int x, int fa) 37 { 38 for (int i=head[x]; i!=-1; i=nxt[i]) 39 { 40 int v = edges[i].v, id = edges[i].id; 41 if (v!=fa) 42 count(v, x), ev[x] = ev[x]^ev[v], eval[id] = ev[v]; 43 } 44 } 45 int main() 46 { 47 freopen("lutree.in","r",stdin); 48 freopen("lutree.out","w",stdout); 49 memset(head, -1, sizeof head); 50 srand(3627), n = read(), m = read(); 51 for (int i=1; i<=n; i++) fa[i] = i; 52 for (int i=1,uf,vf; i<=m; i++) 53 { 54 uf = get(u[i] = read()), vf = get(v[i] = read()); 55 if (uf^vf){ 56 addedge(u[i], v[i], i), fa[uf] = vf; 57 }else{ 58 eval[i] = pr(1ll*rand()*rand()*rand()*rand()*rand(), 1ll*rand()*rand()*rand()*rand()*rand()); 59 } 60 } 61 for (int i=1; i<=m; i++) 62 if (eval[i].first||eval[i].second){ 63 ev[u[i]] = ev[u[i]]^eval[i], ev[v[i]] = ev[v[i]]^eval[i]; 64 } 65 count(1, 0); 66 std::sort(eval+1, eval+m+1); 67 for (int i=1, j=0; i<=m; i=j) 68 { 69 if ((eval[i].first==0)&&(eval[i].second==0)) 70 ans += m-i, j = i+1; 71 else{ 72 for (j=i; j<=m&&eval[i]==eval[j]; j++); 73 ans += 1ll*(j-i-1)*(j-i)/2ll; 74 } 75 } 76 printf("%lld\n",ans); 77 return 0; 78 }
END