[luogu4201][bzoj1063]设计路线【树形DP】

题目描述

Z国坐落于遥远而又神奇的东方半岛上,在小Z的统治时代公路成为这里主要的交通手段。Z国共有n座城市,一些城市之间由双向的公路所连接。非常神奇的是Z国的每个城市所处的经度都不相同,并且最多只和一个位于它东边的城市直接通过公路相连。Z国的首都是Z国政治经济文化旅游的中心,每天都有成千上万的人从Z国的其他城市涌向首都。为了使Z国的交通更加便利顺畅,小Z决定在Z国的公路系统中确定若干条规划路线,将其中的公路全部改建为铁路。我们定义每条规划路线为一个长度大于1的城市序列,每个城市在该序列中最多出现一次,序列中相邻的城市之间由公路直接相连(待改建为铁路)。并且,每个城市最多只能出现在一条规划路线中,也就是说,任意两条规划路线不能有公共部分。当然在一般情况下是不可能将所有的公路修建为铁路的,因此从有些城市出发去往首都依然需要通过乘坐长途汽车,而长途汽车只往返于公路连接的相邻的城市之间,因此从某个城市出发可能需要不断地换乘长途汽车和火车才能到达首都。我们定义一个城市的“不便利值”为从它出发到首都需要乘坐的长途汽车的次数,而Z国的交通系统的“不便利值”为所有城市的不便利值的最大值,很明显首都的“不便利值”为0。小Z想知道如何确定规划路线修建铁路使得Z国的交通系统的“不便利值”最小,以及有多少种不同的规划路线的选择方案使得“不便利值”达到最小。当然方案总数可能非常大,小Z只关心这个天文数字modQ后的值。注意:规划路线1-2-3和规划路线3-2-1是等价的,即将一条规划路线翻转依然认为是等价的。两个方案不同当且仅当其中一个方案中存在一条规划路线不属于另一个方案。

题解

我们可以发现这种修建的方式最终形成的关系很想树链剖分中的轻重链,每个点到首都的不便利值其实就是走过的轻边的数量。对于这个题来说显然我们需要轻边的数量尽可能小,树链剖分中这个数目不超过logn个,所以对于这个题来说第一问的答案一定不超过logn,而这个题与树链剖分还存在不同,因为还可以V型覆盖,就是路径的起终点在同一个点的子树中,对于一个点来说,最多有一个V型覆盖,也最多只有一个简单路径覆盖。
\(f[n][m][i]\)表示以n为根的子树中,答案不超过m,与儿子有i条重边相连的方案数。
其中\(i<=2\)
定义\(g[v][0]\)表示从\(n\)出发走轻边到达父亲节点
\[g[v][0]=f[v][m-1][0]+f[v][m-1][1]+f[v][m-1][2]\]
\(g[v][1]\)表示从n出发走重边到达父亲节点
\[g[v][1]=f[v][m-1][0]+f[v][m-1][1]\]
(没有f[v][m-1][2]是因为如果是走重边上来,那么v这个点下面的儿子中只可能有一个儿子是重边)
007rAy9hgy1g177hnsa43j30j208umx1.jpg

ac代码

#include <bits/stdc++.h>
#define ll long long
#define ms(a, b) memset(a, b, sizeof(a))
#define inf 0x3f3f3f3f
#define N 100005
using namespace std;
template <typename T>
inline void read(T &x) {
    x = 0; T fl = 1;
    char ch = 0;
    while (ch < '0' || ch > '9') {
        if (ch == '-') fl = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    x *= fl;
}
struct edge {
    int to, nt;
}E[N << 1];
int cnt, n, m;
ll f[N][30][4], g[N][5], p;
int H[N];
void add_edge(int u, int v) {
    E[++ cnt] = (edge){v, H[u]}; 
    H[u] = cnt;
}
ll Mod(ll x) {
    if (x && x % p == 0) return p;
    else return x % p;
}
void dfs(int u, int fa) {
    for (int i = 0; i <= 20; i ++) f[u][i][0] = 1;
    for (int e = H[u]; e; e = E[e].nt) {
        int v = E[e].to;
        if (v == fa) continue;
        dfs(v, u);
        for (int j = 0; j <= 20; j ++) {
            if (j) g[v][0] = Mod(f[v][j - 1][0] + f[v][j - 1][1] + f[v][j - 1][2]);
            g[v][1] = Mod(f[v][j][0] + f[v][j][1]);
            f[u][j][2] = Mod(f[u][j][1] * g[v][1] + f[u][j][2] * g[v][0]);
            f[u][j][1] = Mod(f[u][j][0] * g[v][1] + f[u][j][1] * g[v][0]);
            f[u][j][0] = Mod(f[u][j][0] * g[v][0]);

        }
    }
}
int main() {
    read(n); read(m); read(p);
    if (m < n - 1) {
        printf("-1\n-1\n");
        return 0;
    }
    for (int i = 1; i <= m; i ++) {
        int u, v;
        read(u); read(v);
        add_edge(u, v);
        add_edge(v, u);
    }
    ms(f, 0);
    dfs(1, -1);
    for (int i = 0; i <= 20; i ++) {
        if (f[1][i][0] + f[1][i][1]+ f[1][i][2] > 0) {
            printf("%d\n", i);
            printf("%lld\n", (f[1][i][0] + f[1][i][1] + f[1][i][2]) % p);
            return 0;
        }
    }
    printf("-1\n-1\n");
    return 0;
}

转载于:https://www.cnblogs.com/chhokmah/p/10554488.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值