【线性基 集合hash】uoj#138. 【UER #3】开学前的涂鸦
还需要加强分析题目特殊性质,设计对应特殊算法,少想多写大力dfs剪枝不要管MLETLE直接上的能力
红包是一个有艺术细胞的男孩子。
红包由于NOI惨挂心情不好,暑假作业又多,于是他开始在作业本上涂鸦。
一开始,他在纸上画了一棵 nn 个节点的树。但是他觉得这样的画太简单了,体现不出他高超的绘画功底,于是他又额外画上了 kk 条边。
然而他觉得这样画面太复杂,于是想删去一些边使得这个无向图仍然是连通的。
请帮红包求出删边的方案数。两个方案被认为是不同的当且仅当存在一条边在其中一组中被删而另一组中没有。(什么边都不删也算一种方案)
输入格式
第一行两个整数,n,kn,k。保证 1≤n≤105,k≥01≤n≤105,k≥0。
接下来 n−1n−1 行,描述了红包最开始画的那颗树。每行两个整数 v,uv,u 表示 vv 和 uu 之间有一条无向边。
接下来 kk 行,描述了红包后来加的边。每行两个整数 v,uv,u 表示红包在 vv 和 uu 之间又加上了一条边。数据保证树中原有的边不会被再添加一次且 v≠uv≠u。
保证 1≤v,u≤n1≤v,u≤n。
输出格式
一个整数,表示方案数。你只用输出答案对 998244353998244353(7×17×223+17×17×223+1,一个质数)取模后的值。
题目分析
关于离线维护动态图连通性有一种常见套路:处理出原图的一颗dfs树,再为每一条非树边随机分配一个权值,剩下的树边权值就是所有经过它的非树边权值异或和。那么选出的边集对原图连通性没有影响当且仅当该边集的权值两两线性无关。
之前做过一题的套路就是这样的:【思维题 集合hash 树上差分】11.5撸树
关于这个随机化算法的证明,网上找到两篇高质量的证明 “图不连通 iff 删掉边集的权值线性相关”,
对于这题,首先注意到k不大,那么就不需要常规做法的双哈希pair,而是直接将第i条非树边权值设为$2^{i-1}$.这样一来就能够在保证正确性的前提下减少常数。第二,如何求一个集合的线性无关子集个数?注意到这里的线性无关子集大小与集合元素种类都是和k同阶的。然后就可以用 算法七 的暴力dfs寻找线性无关子集数量,而因为“线性无关”这道剪枝“十分有力”,所以uoj榜上这个做法吊打标算……
1 #include<bits/stdc++.h> 2 #define MO 998244353 3 typedef unsigned long long ull; 4 const int maxn = 100035; 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 struct node 13 { 14 ull val,cnt; 15 }a[maxm]; 16 struct LinearBasis 17 { 18 ull a[102]; 19 bool insert(ull x) 20 { 21 for (int i=64,chk=0; i>=0&&!chk; i--) 22 if ((x>>i)&1){ 23 if (a[i]) x ^= a[i]; 24 else a[i] = x, chk = true; 25 } 26 return x > 0; 27 } 28 }; 29 int n,k,m,cnt,u[13],v[13]; 30 int edgeTot,head[maxn],nxt[maxm]; 31 ull eval[maxm],enod[maxn]; 32 long long ans; 33 34 int read() 35 { 36 char ch = getchar(); 37 int num = 0, fl = 1; 38 for (; !isdigit(ch); ch=getchar()) 39 if (ch=='-') fl = -1; 40 for (; isdigit(ch); ch=getchar()) 41 num = (num<<1)+(num<<3)+ch-48; 42 return num*fl; 43 } 44 void addedge(int u, int v, int id) 45 { 46 edges[++edgeTot] = Edge(v, id), nxt[edgeTot] = head[u], head[u] = edgeTot; 47 edges[++edgeTot] = Edge(u, id), nxt[edgeTot] = head[v], head[v] = edgeTot; 48 } 49 void color(int x, int fa) 50 { 51 for (int i=head[x]; i!=-1; i=nxt[i]) 52 { 53 int v = edges[i].v, id = edges[i].id; 54 if (v==fa) continue; 55 color(v, x), enod[x] = enod[x]^enod[v], eval[id] = enod[v]; 56 } 57 } 58 void dfs(int x, LinearBasis s, int c) 59 { 60 if (x==cnt+1) ans = (ans+c)%MO; 61 else{ 62 dfs(x+1, s, c); 63 if (s.insert(a[x].val)) 64 dfs(x+1, s, 1ll*c*a[x].cnt%MO); 65 } 66 } 67 int main() 68 { 69 memset(head, -1, sizeof head); 70 n = read(), k = read(); 71 for (int i=1; i<n; i++) 72 addedge(read(), read(), i); 73 for (int i=1; i<=k; i++) 74 u[i] = read(), v[i] = read(), eval[i+n-1] = (ull)1ll<<i, 75 enod[u[i]] = enod[u[i]]^eval[i+n-1], enod[v[i]] = enod[v[i]]^eval[i+n-1]; 76 color(1, 0); 77 m = n+k-1; 78 std::sort(eval+1, eval+m+1); 79 for (int i=1,j=1; i<=m; i=j) 80 if (eval[i]){ 81 for (j=i; eval[j]==eval[i]&&j<=m; j++); 82 a[++cnt].val = eval[i], a[cnt].cnt = j-i; 83 }else ++j; 84 dfs(1, LinearBasis(), 1); 85 printf("%lld\n",ans); 86 return 0; 87 }
END