HDU 5977 Garden of Eden(树分治)

xiaoxiao2021-02-27  351

Garden of Eden

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others) Total Submission(s): 593    Accepted Submission(s): 172 Problem Description When God made the first man, he put him on a beautiful garden, the Garden of Eden. Here Adam lived with all animals. God gave Adam eternal life. But Adam was lonely in the garden, so God made Eve. When Adam was asleep one night, God took a rib from him and made Eve beside him. God said to them, “here in the Garden, you can do everything, but you cannot eat apples from the tree of knowledge.” One day, Satan came to the garden. He changed into a snake and went to live in the tree of knowledge. When Eve came near the tree someday, the snake called her. He gave her an apple and persuaded her to eat it. Eve took a bite, and then she took the apple to Adam. And Adam ate it, too. Finally, they were driven out by God and began a hard journey of life. The above is the story we are familiar with. But we imagine that Satan love knowledge more than doing bad things. In Garden of Eden, the tree of knowledge has n apples, and there are k varieties of apples on the tree. Satan wants to eat all kinds of apple to gets all kinds of knowledge.So he chooses a starting point in the tree,and starts walking along the edges of tree,and finally stops at a point in the tree(starting point and end point may be same).The same point can only be passed once.He wants to know how many different kinds of schemes he can choose to eat all kinds of apple. Two schemes are different when their starting points are different or ending points are different.   Input There are several cases.Process till end of input. For each case, the first line contains two integers n and k, denoting the number of apples on the tree and number of kinds of apple on the tree respectively. The second line contains n integers meaning the type of the i-th apple. Types are represented by integers between 1 and k . Each of the following n-1 lines contains two integers u and v,meaning there is one edge between u and v.1≤n≤50000, 1≤k≤10   Output For each case output your answer on a single line.   Sample Input 3 2 1 2 2 1 2 1 3   Sample Output 6   Source 2016ACM/ICPC亚洲区大连站-重现赛(感谢大连海事大学)   Recommend wange2014   |   We have carefully selected several similar problems for you:   6022  6021  6020  6019  6018   

题意:

给一棵节点数为n,节点种类为k的无根树,问其中有多少种不同的简单路径,可以满足路径上经过所有k种类型的点?(a->b与b->a算作两条路径,起点与终点也可以相同)

思路:

现场赛的时候k的大小是7,当时看到这题也没多想就树形dp水过了。现在重现赛k改成了10,这时候用树形dp,无论是时间还是空间复杂度都很爆炸。后来听说这题的正解是树分治,于是就学习了一波,然后重新来做这道题,关于树分治的内容在我上一篇博客中详细介绍了,链接: http://blog.csdn.net/bahuia/article/details/53066373 这题运用树的点分治算法,与POJ-1741的区别就在于后者是求长度小于等于k的路径数目,而这道题是求经过所有种类点的路径,状压一下,也就是求状态为(1<<k)-1的路径数目,其实本质上是一样的,只是从路径权值的加和变成了路径状态的或运算。 这题的难点在于cal()函数,也就是将问题转化成了已知x个数a1,a2,...ax,求其中有多少点对的或运算的和为(1<<k)-1,因为这些都是二进制状态,并没有直接的大小关系,所以POJ-1741那题排序的算法就不能用了,这里我们必须另外想一个O(nlogn)级别的算法。 我们枚举每一个其中的每一个数x,想找到数组中有多少数和x的或运算的和为(1<<k)-1,也就是找到可以包含((1<<k)-1)^x的数,这时候可以反向考虑,先枚举x的子集,然后再与(1<<k)-1进行异或运算,就可以找到了所有的情况。 具体细节看代码。 题解参考自: here

之前没怎么做过这类题目,好DIAO的样子,回头总结!!!

下面是当时比赛时候WYP敲的代码,当时我都没看。。。

#include<bits/stdc++.h> using namespace std; #define maxn 100100 #define maxm 200005 #define rd(x) scanf("%d", &x) #define rd2(x, y) scanf("%d%d", &x, &y) #define mod 1000000007 int V[maxn]; struct Edge{ int next,v; }edge[maxn*4]; int head[maxn],vis[maxn], tot; int n,k; long long int res; int f[15], sz[maxn], mx[maxn]; void addedge(int u, int v){ edge[tot].v = v; edge[tot].next = head[u]; head[u] = tot++; } map<int, long long int> mp[maxn]; void init(){ tot = 0; memset(head, -1, sizeof(head)); memset(vis, 0, sizeof(vis)); for(int i = 0; i < 10; i++){ f[i+1] = 1 << i; } for(int i = 1; i <= n ; i++){ mp[i].clear(); } } ///统计子树大小 void dfsize(int x, int fa){ sz[x] = 1; mx[x] = 0; for(int i = head[x]; i != -1; i = edge[i].next){ int v = edge[i].v; if(v == fa || vis[v]) continue; dfsize(v, x); sz[x] += sz[v]; if(sz[v] > mx[v]) mx[x] = sz[v]; } } void dfsroot(int r, int x, int fa, int &root, int &mm){ if(sz[r] - sz[x] > mx[x]) mx[x] = sz[r] - sz[x]; if(mx[x] < mm) mm = mx[x], root = x; for(int i = head[x]; i != -1; i = edge[i].next){ int v = edge[i].v; if(v == fa || vis[v]) continue; dfsroot(r, v, x, root, mm); } } vector<long long int> vec; void cal(int x, int fa, int kk, int root){ // 计算当前子树中合法的点对数 int fx = f[V[x]]; kk = (kk | fx); int k2 = (1 << k) -1 - kk; int ks = (1 << k) -1; if(x != root) { for(map<int, long long int>::iterator it = mp[root].begin(); it != mp[root].end(); it++){ int k3 = (*it).first; if((k3 & k2) == k2) res += (*it).second; } vec.push_back(kk); } for(int i = head[x]; i != -1; i = edge[i].next){ int v = edge[i].v; if(v == fa || vis[v]) continue; cal(v, x, kk, root); if(x == root){ int k2 = (1 << k) -1; for(int i =0 ; i < vec.size(); i++){ mp[root][vec[i]]++; //if(vec[i] == k2) res++; } vec.clear(); } } } void solve(int x){ dfsize(x, x); int mm = n + 1; int root; dfsroot(x, x, x, root, mm); vec.clear(); mp[root][0]++; cal(root, root, 0, root); vis[root] = 1; for(int i = head[root]; i != -1; i = edge[i].next){ int v = edge[i].v; if(vis[v]) continue; solve(v); } } int main() { int a, b; while(~scanf("%d%d", &n, &k)){ for(int i =1; i <= n; i++){ rd(V[i]); } init(); res = 0; for(int i =1; i < n; i++){ rd2(a, b); addedge(a, b); addedge(b, a); } if(k == 1){ printf("%lld\n", 1LL*n*n); continue; } solve(1); res = res*2; if(k == 1) res += n; printf("%lld\n", res); } return 0; }

转载请注明原文地址: https://www.6miu.com/read-1632.html

最新回复(0)