【思维题 集合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

posted @ 2018-11-05 20:35  AntiQuality  阅读(364)  评论(0编辑  收藏  举报