BZOJ1063道路设计
2016-01-21 15:46
169 查看
提示:
1. 这是一棵树
2. 还记得树链剖分关于轻边logn的证明吗 , 对这个题有没有什么启示呢?
思路留给大家思考 ,
代码后我将为两种做法作出详细解释:
No.1 :
No.2 :
这个题是树形DP ,根据树链剖分的证明 , 分成的链数不得多于logn条 , 所以我们可以枚举第一问所求的答案 , 如果此时是可行的那么一定是最小的。 还有一个往往被忽视的原因是 , 如果不枚举链数 , 那么其实无法进行DP , 因为总体最优的状态并不要求局部最优。
第一种做法是网上比较流行的 , 在此可以将状态表示为:
f[i][j][k]→ 编号为i 的结点 , 在最大链深度(也就是此时的答案)不超过j , 向下连出k条链时的方案数。 (但注意两份代码下标的顺序不同。 )
那么转移根据代表的意义是不难写出的:
我们设
Tu=∑0≤k≤2f[u][j−1][k]Gu=∑0≤k≤1f[u][j][k]
那么就有:
f[i][j][0]=∏u∈sonsTu
f[i][j][1]=∑u∈sonsGu×∏v∈sons,v≠uTv
f[i][j][2]=∑u∈sons∑v∈sons,v≠u⎛⎝Gu×Gv×∏k∈sons,k≠u,k≠vTv⎞⎠
这个式子是巨难求 , 所以要优化求法。 可以考虑在每个结点的儿子间进行背包DP来优化到线性。也就是用目前的f[i][j][] 去互相影响。 这其实不难理解。
下面谈谈这个题的另一种实现方法 , 左儿子右兄弟表示一棵树:
这里就不多讲这种表现形式了 , 我们直接看状态:
f[i][j][k]→ 对于结点i , 此时其链深度(就是此时以i为根的答案)为j , i这一代结点还能连 k 条链的方案数。
解释一下什么叫做一代结点 , 因为左儿子右兄弟的表示法并不能体现原来图的父子关系 , 我们称在原图中有同样父亲的结点为一代结点。
每一次决策是讨论结点 i 是否与自己的父亲连边 , 然后决定自己的儿子们要跟自己连接几条边。 这有点像父亲结点分配任务然后儿子结点们逐步实现任务。
对于每次决策 , 连边与不连边的转移方程如下:
G1=∑0≤i≤1f[leftSon][j][i]∗f[rightSon][j][k−1]
G2=∑0≤i≤2f[leftSon][j−1][i]∗f[rightSon][j][k]
那么此时 , 答案为G1+G2 。
不难发现这样做简化了很多 , 左儿子右兄弟是一种表示方法 , 这种表示方法可以简化DP的转移。 这是由于这种表示方法充分的应用了DP的状态。
最后值得一提的是 , 这个题在判断可行的时候并不能直接看此时的答案是否为0(可能本身余数就是0) , 因而我用了一个数组表示此时的可行性。
1. 这是一棵树
2. 还记得树链剖分关于轻边logn的证明吗 , 对这个题有没有什么启示呢?
思路留给大家思考 ,
代码后我将为两种做法作出详细解释:
No.1 :
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <vector> #include <deque> #include <queue> #include <list> #include <algorithm> using namespace std; typedef long long ll; const int maxn = 110000; __inline int re() { int n; scanf("%d" , &n); return n; } ll n , m , modu; vector<int> g[maxn]; ll d[maxn][15][3]; int c[maxn][15][3]; bool dp(int u , int m , int fa) { d[u][m][0] = 1; c[u][m][0] = 1; d[u][m][1] = d[u][m][2] = 0; if(g[u].size()==1 && g[u][0]==fa) return true; for(int i=0;i<g[u].size();i++) { int v = g[u][i]; if(v == fa) continue; dp(v , m , u); ll f = (d[v][m-1][0] + d[v][m-1][1] + d[v][m-1][2])%modu; ll r = (d[v][m][0]+d[v][m][1])%modu; bool p = (c[v][m-1][0] | c[v][m-1][1] | c[v][m-1][2]); bool k = (c[v][m][0] | c[v][m][1]); d[u][m][2] = (d[u][m][2]*f + d[u][m][1]*r)%modu; c[u][m][2] = (c[u][m][2]&p) | (c[u][m][1]&k); d[u][m][1] = (d[u][m][1]*f + d[u][m][0]*r)%modu; c[u][m][1] = (c[u][m][1]&p) | (c[u][m][0]&k); d[u][m][0] = (d[u][m][0] * f)%modu; c[u][m][0] &= p; } return c[u][m][0] || c[u][m][1] || c[u][m][2]; } int main() { #ifndef ONLINE_JUDGE freopen("in","r",stdin); #endif n = re(); m = re(); scanf("%lld" , &modu); if(m!=n-1) { puts("-1\n-1"); return 0; } while(m--) { int a = re() , b = re(); g[a].push_back(b); g[b].push_back(a); } for(int i=1;;i++) if(dp(1 , i , -1)) { printf("%d\n%lld\n" , i-1 , (d[1][i][0]+d[1][i][1]+d[1][i][2])%modu); break; } return 0; }
No.2 :
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <vector> #include <deque> #include <queue> #include <list> #include <algorithm> using namespace std; typedef long long ll; const int maxn = 110000; __inline int re() { int n; scanf("%d" , &n); return n; } ll n , m , modu; vector<int> g[maxn]; int ch[maxn][2]; ll d[15][maxn][3]; int c[15][maxn][3]; int v[15][maxn][3]; void dfs(int u , int F) { int last = 0; for(int i=0;i<g[u].size();i++) { int v = g[u][i]; if(v==F) continue; ch[u][0] = v; ch[v][1] = last; last = v; dfs(v , u); } } void tangent(ll& a , ll b) { a = (a+b)%modu; } ll dp(int m , int u , int l) { ll& res = d[m][u][l]; int& b = c[m][u][l]; if(v[m][u][l]) return res; v[m][u][l] = 1; if(!u) { b = (m>=0 && !l); return res = (m>=0 && !l); } int b1 = 0 , b2 = 0; if(l) { ll s = 0; for(int i=0;i<2;i++) tangent(s , dp(m , ch[u][0] , i)) , b1 |= c[m][ch[u][0]][i]; res = (s*dp(m , ch[u][1] , l-1))%modu; b1 &= c[m][ch[u][1]][l-1]; } if(m) { ll s = 0; for(int i=0;i<3;i++) tangent(s , dp(m-1 , ch[u][0] , i)) , b2 |= c[m-1][ch[u][0]][i]; s = (s * dp(m , ch[u][1] , l ))%modu , b2 &= c[m][ch[u][1]][l]; res = (res + s)%modu; } b = b1 | b2; return res; } int main() { #ifndef ONLINE_JUDGE freopen("in","r",stdin); #endif n = re(); m = re(); scanf("%lld" , &modu); if(m!=n-1) { puts("-1\n-1"); return 0; } while(m--) { int a = re() , b = re(); g[a].push_back(b); g[b].push_back(a); } dfs(1 , -1); for(int i=1;;i++) if(dp(i , 1 , 0) || c[i][1][0]) { printf("%d\n%lld\n" , i-1 , d[i][1][0]); break; } return 0; }
这个题是树形DP ,根据树链剖分的证明 , 分成的链数不得多于logn条 , 所以我们可以枚举第一问所求的答案 , 如果此时是可行的那么一定是最小的。 还有一个往往被忽视的原因是 , 如果不枚举链数 , 那么其实无法进行DP , 因为总体最优的状态并不要求局部最优。
第一种做法是网上比较流行的 , 在此可以将状态表示为:
f[i][j][k]→ 编号为i 的结点 , 在最大链深度(也就是此时的答案)不超过j , 向下连出k条链时的方案数。 (但注意两份代码下标的顺序不同。 )
那么转移根据代表的意义是不难写出的:
我们设
Tu=∑0≤k≤2f[u][j−1][k]Gu=∑0≤k≤1f[u][j][k]
那么就有:
f[i][j][0]=∏u∈sonsTu
f[i][j][1]=∑u∈sonsGu×∏v∈sons,v≠uTv
f[i][j][2]=∑u∈sons∑v∈sons,v≠u⎛⎝Gu×Gv×∏k∈sons,k≠u,k≠vTv⎞⎠
这个式子是巨难求 , 所以要优化求法。 可以考虑在每个结点的儿子间进行背包DP来优化到线性。也就是用目前的f[i][j][] 去互相影响。 这其实不难理解。
下面谈谈这个题的另一种实现方法 , 左儿子右兄弟表示一棵树:
这里就不多讲这种表现形式了 , 我们直接看状态:
f[i][j][k]→ 对于结点i , 此时其链深度(就是此时以i为根的答案)为j , i这一代结点还能连 k 条链的方案数。
解释一下什么叫做一代结点 , 因为左儿子右兄弟的表示法并不能体现原来图的父子关系 , 我们称在原图中有同样父亲的结点为一代结点。
每一次决策是讨论结点 i 是否与自己的父亲连边 , 然后决定自己的儿子们要跟自己连接几条边。 这有点像父亲结点分配任务然后儿子结点们逐步实现任务。
对于每次决策 , 连边与不连边的转移方程如下:
G1=∑0≤i≤1f[leftSon][j][i]∗f[rightSon][j][k−1]
G2=∑0≤i≤2f[leftSon][j−1][i]∗f[rightSon][j][k]
那么此时 , 答案为G1+G2 。
不难发现这样做简化了很多 , 左儿子右兄弟是一种表示方法 , 这种表示方法可以简化DP的转移。 这是由于这种表示方法充分的应用了DP的状态。
最后值得一提的是 , 这个题在判断可行的时候并不能直接看此时的答案是否为0(可能本身余数就是0) , 因而我用了一个数组表示此时的可行性。
相关文章推荐
- C++动态规划之最长公子序列实例
- C++动态规划之背包问题解决方法
- C#使用动态规划解决0-1背包问题实例分析
- 动态规划
- C++ 动态规划
- DP(动态规划) 解游轮费用问题
- 动态规划的用法——01背包问题
- 动态规划的用法——01背包问题
- 《收集苹果》 动态规划入门
- 《DNA比对》蓝桥杯复赛试题
- 《背包问题》 动态规划
- 初学ACM - 半数集(Half Set)问题 NOJ 1010 / FOJ 1207
- 关于爬楼梯的动态规划算法
- 动态规划 --- hdu 1003 **
- DP问题各种模型的状态转移方程
- 0-1背包解题过程
- 背包问题
- USACO 3.2.2:Stringsobits
- 字符串编辑距离
- HDU ACM Step 2.2.2 Joseph(约瑟夫环问题)