提示:
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]
那么就有:
这个式子是巨难求 , 所以要优化求法。 可以考虑在每个结点的儿子间进行背包DP来优化到线性。也就是用目前的 f[i][j][] 去互相影响。 这其实不难理解。
下面谈谈这个题的另一种实现方法 , 左儿子右兄弟表示一棵树:
这里就不多讲这种表现形式了 , 我们直接看状态:
f[i][j][k]→ 对于结点 i , 此时其链深度(就是此时以 i 为根的答案)为 j , i 这一代结点还能连 k 条链的方案数。
解释一下什么叫做一代结点 , 因为左儿子右兄弟的表示法并不能体现原来图的父子关系 , 我们称在原图中有同样父亲的结点为一代结点。
每一次决策是讨论结点 i 是否与自己的父亲连边 , 然后决定自己的儿子们要跟自己连接几条边。 这有点像父亲结点分配任务然后儿子结点们逐步实现任务。
对于每次决策 , 连边与不连边的转移方程如下:
那么此时 , 答案为 G1+G2 。
不难发现这样做简化了很多 , 左儿子右兄弟是一种表示方法 , 这种表示方法可以简化DP的转移。 这是由于这种表示方法充分的应用了DP的状态。
最后值得一提的是 , 这个题在判断可行的时候并不能直接看此时的答案是否为0(可能本身余数就是0) , 因而我用了一个数组表示此时的可行性。