HDU 5647 DZY Loves Connecting 树形dp
2016-05-09 00:53
369 查看
题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=5647题解:
令dp[u][0]表示u所在的子树中所有的包含i的集合数,设u的儿子为vi,则易知dp[u][0]=(dp[v1][0]+1)*...*(dp[vk][0]+1)。令dp[u][1]表示u所在的子树中所有的包含i的集合数的大小的和,则有dp[u][1]=dp[u][1]*(dp[v][0]+1)+dp[v][1]*dp[u][0];
其中dp[u][1]*(dp[v][0]+1)表示新引入v的(dp[v][0]+1)个组合的时候,左边已知的贡献值(dp[u][1],即已知的包含节点i的集合数的大小的和)增倍之后的量。
dp[v][1]*dp[u][0]则与上面刚好反过来,考虑对于已知的dp[u][0]种集合数,儿子v的贡献值增倍后的量。
则最后的答案为dp[1][1]+...+dp
[1]
ps: 如果还不明白dp[u][0],dp[u][1]表示的含义,可以用代码跑一下简单的样例,把它们都打印出来,应该会好理解一些。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int maxn = 2e5 + 10; const int mod = 1e9 + 7; typedef long long LL; struct Edge { int v, ne; Edge(int v, int ne) :v(v), ne(ne) {} Edge() {} }egs[maxn]; int head[maxn], tot; int n; void addEdge(int u, int v) { egs[tot] = Edge(v, head[u]); head[u] = tot++; } LL dp[maxn][2]; void solve(int u) { dp[u][0] = dp[u][1] = 1; int p = head[u]; while (p != -1) { Edge& e = egs[p]; solve(e.v); dp[u][1] = (dp[u][1] * (dp[e.v][0] + 1) + dp[e.v][1] * dp[u][0]) % mod; dp[u][0] = dp[u][0] * (dp[e.v][0] + 1)%mod; p = e.ne; } } void init() { memset(head, -1, sizeof(head)); tot = 0; } int main() { int tc; scanf("%d", &tc); while (tc--) { init(); scanf("%d", &n); for (int i = 2; i <= n; i++) { int v; scanf("%d", &v); addEdge(v, i); } solve(1); LL ans = 0; for (int i = 1; i <= n; i++) { //printf("dp[%d][1]:%d\n", i,dp[i][1]); ans += dp[i][1]; ans %= mod; } printf("%lld\n", ans); } return 0; } /* 1 //testcase 3 1 1 */
以下是按官方题解的思路写的代码,但是wa了,当(dp[u]+1)%mod==0的时候逆元就求不出来了。
这样写就t了,说明数据确实会出现这种情况
官方题解:
#pragma comment(linker, "/STACK:102400000,102400000") #include<iostream> #include<cstring> #include<cstdio> using namespace std; typedef long long LL; const int maxn = 2e5 + 10; const int mod = 1e9 + 7; struct Edge { int v, ne; Edge(int v,int ne):v(v),ne(ne){} Edge(){} }egs[maxn*2]; int head[maxn], tot; void addEdge(int u, int v) { egs[tot] = Edge(v, head[u]); head[u] = tot++; } void gcd(LL a, LL b, LL &d, LL &x, LL &y) { if (!b) { d = a; x = 1; y = 0; } else { gcd(b, a%b, d, y, x); y -= x*(a / b); } // x = (x%mod + mod) % mod; // y = (y%mod + mod) % mod; } LL invMod(LL a, LL b) { LL d, x, y; gcd(a, b, d, x, y); return (x%mod+mod)%mod; } int n; LL dp[maxn]; int fa[maxn]; //自底向上 void dfs1(int u) { dp[u] = 1; int p = head[u]; while (p != -1) { Edge& e = egs[p]; dfs1(e.v); dp[u] *= (dp[e.v] + 1); dp[u] %= mod; p = e.ne; } } //自顶向下 void dfs2(int u) { if (fa[u]) { LL tmp = dp[fa[u]] * invMod(dp[u] + 1, (LL)mod) % mod; dp[u] = dp[u] * (tmp + 1) % mod; } int p = head[u]; while(p != -1) { Edge& e = egs[p]; dfs2(e.v); p = e.ne; } } void init() { fa[1] = 0; memset(dp, 0, sizeof(dp)); memset(head, -1, sizeof(head)); tot = 0; } int main() { // freopen("data_in.txt", "r", stdin); int tc; scanf("%d", &tc); while (tc--) { scanf("%d", &n); init(); for (int i = 2; i <= n; i++) { int x; scanf("%d", &x); addEdge(x, i); fa[i] = x; } dfs1(1); dfs2(1); LL ans = 0; for (int i = 1; i <= n; i++) { ans += dp[i]; ans %= mod; } printf("%lld\n", ans); } return 0; } /* */
但是!这种情况比较特殊,是可以单独处理一下的,对于节点u,如果在第一次dfs中它的儿子中有(dp[v]+1)%mod==0,那么说明在第一次dfs中dp[u]=0,也就是说dp[u]+1==1,u对它的父亲是没有贡献的!,那么在第二次dfs中,dp[u]实际保存的数就是整颗树中不包含u这颗子树的所有节点中包含u的父亲的集合的个数,所以只要算(dp[u]+1)*spec(spec表示v所有的兄弟的(dp[vi]+1)的乘积,不包含v本身),就可以了,具体看代码。
#pragma comment(linker, "/STACK:102400000,102400000") #include<iostream> #include<cstring> #include<cstdio> using namespace std; typedef long long LL; const int maxn = 2e5 + 10; const int mod = 1e9 + 7; struct Edge { int v, ne; Edge(int v,int ne):v(v),ne(ne){} Edge(){} }egs[maxn*2]; int head[maxn], tot; void addEdge(int u, int v) { egs[tot] = Edge(v, head[u]); head[u] = tot++; } void gcd(LL a, LL b, LL &d, LL &x, LL &y) { if (!b) { d = a; x = 1; y = 0; } else { gcd(b, a%b, d, y, x); y -= x*(a / b); } } LL invMod(LL a, LL b) { LL d, x, y; gcd(a, b, d, x, y); return (x%mod+mod)%mod; } int n; LL dp[maxn]; int fa[maxn]; void dfs1(int u) { dp[u] = 1; int p = head[u]; while (p != -1) { Edge& e = egs[p]; dfs1(e.v); dp[u] *= (dp[e.v] + 1); dp[u] %= mod; p = e.ne; } } void dfs2(int u,LL spec) { if (fa[u]) { LL tmp; if((dp[u]+1)%mod) tmp=dp[fa[u]] * invMod(dp[u] + 1, mod) % mod; else { tmp = (dp[fa[u]]+1) * spec % mod; } dp[u] = dp[u] * (tmp + 1) % mod; } int p = head[u]; spec = 1; int po = -1,flag=0; while(p != -1) { Edge& e = egs[p]; if ((dp[e.v] + 1) % mod == 0) { if(po==-1) po = e.v; else { flag = 1; dfs2(e.v, 0); } } else { spec *= (dp[e.v] + 1); spec %= mod; dfs2(e.v, spec); } p = e.ne; } if (po!=-1) { if (!flag) dfs2(po, spec); else dfs2(po, 0); } } void init() { fa[1] = 0; memset(dp, 0, sizeof(dp)); memset(head, -1, sizeof(head)); tot = 0; } int main() { //freopen("data_in.txt", "r", stdin); int tc; scanf("%d", &tc); while (tc--) { scanf("%d", &n); init(); for (int i = 2; i <= n; i++) { int x; scanf("%d", &x); addEdge(x, i); fa[i] = x; } dfs1(1); dfs2(1,0); LL ans = 0; for (int i = 1; i <= n; i++) { ans += dp[i]; ans %= mod; } printf("%lld\n", ans); } return 0; } /* */
相关文章推荐
- Java Web基础小结之Jsp JavaBean
- p3p种cookie
- 进程不具有此命名空间的访问权限
- jvm内存调优
- Top K Frequent Elements=-leetcode
- Java架构中的常见设计模式
- 删繁就简 - 云和恩墨的一道面试题解析
- HDOJ(HDU) 2503 a/b + c/d(最大公约数问题)
- HDOJ(HDU) 2503 a/b + c/d(最大公约数问题)
- 动态代理模式
- linux安装jre
- 【杂】指针,*,&
- 生活随笔:创业的焦虑
- 20145309实验5
- 为何使用Asp.net开发,部署我却选择Apache而不用IIS
- Java NIO 理解
- 20145309第十周学习总结
- 算法训练 JAM计数法
- [置顶] [CSAPP笔记][第二章信息的表示和处理]
- [CSAPP笔记][第二章信息的表示和处理]