您的位置:首页 > 其它

BZOJ1063道路设计

2016-01-21 15:46 169 查看
提示:

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) , 因而我用了一个数组表示此时的可行性。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  BZOJ 动态规划