BZOJ1063道路设计

提示:
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=0k2f[u][j1][k]Gu=0k1f[u][j][k]
那么就有:

f[i][j][0]=usonsTu

f[i][j][1]=usonsGu×vsons,vuTv

f[i][j][2]=usonsvsons,vuGu×Gv×ksons,ku,kvTv

这个式子是巨难求 , 所以要优化求法。 可以考虑在每个结点的儿子间进行背包DP来优化到线性。也就是用目前的 f[i][j][] 去互相影响。 这其实不难理解。

下面谈谈这个题的另一种实现方法 , 左儿子右兄弟表示一棵树:
这里就不多讲这种表现形式了 , 我们直接看状态:

f[i][j][k] 对于结点 i , 此时其链深度(就是此时以 i 为根的答案)为 j i 这一代结点还能连 k 条链的方案数。

     解释一下什么叫做一代结点 , 因为左儿子右兄弟的表示法并不能体现原来图的父子关系 , 我们称在原图中有同样父亲的结点为一代结点。

     每一次决策是讨论结点 i 是否与自己的父亲连边 , 然后决定自己的儿子们要跟自己连接几条边。 这有点像父亲结点分配任务然后儿子结点们逐步实现任务。

对于每次决策 , 连边与不连边的转移方程如下:

G1=0i1f[leftSon][j][i]f[rightSon][j][k1]

G2=0i2f[leftSon][j1][i]f[rightSon][j][k]

那么此时 , 答案为 G1+G2

不难发现这样做简化了很多 , 左儿子右兄弟是一种表示方法 , 这种表示方法可以简化DP的转移。 这是由于这种表示方法充分的应用了DP的状态。

最后值得一提的是 , 这个题在判断可行的时候并不能直接看此时的答案是否为0(可能本身余数就是0) , 因而我用了一个数组表示此时的可行性。

你可能感兴趣的:(动态规划,bzoj)