★【树型动态规划】【NOI2008】奥运物流

【问题描述】  
2008 北京奥运会即将开幕,举国上下都在为这一盛事做好准备。为了高效率、成功地举办奥运会,对物流系统进行规划是必不可少的。  
物流系统由若干物流基站组成,以 1...N 进行编号。每个物流基站 i 都有且仅有一个后继基站 Si,而可以有多个前驱基站。基站 i 中需要继续运输的物资都将被运往后继基站 Si,显然一个物流基站的后继基站不能是其本身。编号为 1 的从任何物流基站都可将物资运往控制基站。  
物流基站称为控制基站,注意控制基站也有后继基站,以便在需要时进行物资的流通。在物流系统中,高可靠性与低成本是主要设计目。对于基站 i,我们定义其“可靠性” R (i ) 如下:  
设物流基站 i 有 w 个前驱基站 P1, P2, Pw,即这些基站以 i 为后继基站,则基站 i 的可靠性 R(i)满足下式: 


其中 Ci 和 k 都是常实数且恒为正,且有 k 小于 1。  
整个系统的可靠性与控制基站的可靠性正相关,我们的目标是通过修改物流系统,即更改某些基站的后继基站,使得控制基站的可靠性 R(1)尽量大。但由于经费限制,最多只能修改 m 个基站的后继基站,并且,控制基站的后继基站不可被修改。因而我们所面临的问题就是,如何修改不超过 m 个基站的后继,使得控制基站的可靠性 R(1)最大化。  
【输入格式】  
输入文件 trans.in 第一行包含两个整数与一个实数,N, m, k。其中 N 表示基站数目,m 表示最多可修改的后继基站数目,k 分别为可靠性定义中的常数。  
第二行包含 N 个整数,分别是 S1, S2...SN,即每一个基站的后继基站编号。  
第三行包含 N 个正实数,分别是 C1, C2...CN,为可靠性定义中的常数。  
【输出格式】  
输出文件 trans.out 仅包含一个实数,为可得到的最大 R(1)。精确到小数点两位。  
【输入样例】  
4 1 0.5  
2 3 1 3  
10.0 10.0 10.0 10.0  
【输出样例】  
30.00  
【样例说明】  
原有物流系统如左图所示,4 个物流基站的可靠性依次为 22.8571,21.4286, 25.7143,10。  

最优方案为将 2 号基站的后继基站改为 1 号,如右图所示。 此时 4 个基站的可靠性依次为 30,25,15,10。  
【数据规模和约定】  
本题的数据,具有如下分布:

对于所有的数据,满足 m ≤ N ≤ 60,Ci ≤ 106,0.3 ≤ k < 1,请使用双精度实数,无需考虑由此带来的误差。  
考试的时候,直接没管这道题,于是0分,后来听说贪心能得70分,很无语……

整个图就是一棵树,树根上是一个环,若设环的长度为len,那么整个答案为:

那么每个点离根越近答案越优,于是m次修改一定是将其中m个点的后继基站改为1。

用树型动态规划解决此问题:
首先枚举根部的环的长度,对每一个长度的环都进行一次动态规划,取出所有的最大值(注意修改环的长度可能会造成一次修改机会的消耗)作为最终答案。

用f[u][c][d]表示以u为根的子树中,修改c次,且u到根的距离为d的最大值(所有子树均不考虑1的后继边)。
对于非1的u,转移方程如下:

可以令g[u][c][d] = max{f[u][c][d + 1], f[u][c][1]}以简化方程,
并且可以发现右边的两项其实就是对c的一个最大分配,所以可以用多重背包问题解决。
但要注意u = 1时,方程右边只有第一项,并且其中的g[u][c][d]为f[u][c][1]。
代码:

/**************************\
 * @prob: NOI2008 trans   *
 * @auth: Wang Junji      *
 * @stat: Accepted.       *
 * @date: May. 21st, 2012 *
 * @memo: 树型动态规划      *
\**************************/
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <string>

const int maxN = 80;
double f[maxN][maxN][maxN], g[maxN][maxN][maxN];
double C[maxN], F[maxN], K[maxN], ans;
int pre[maxN], n, m;

void Dp(int u, int d)
{
    for (int v = 2; v < n + 1; ++v) if (pre[v] == u) Dp(v, d + 1);
    for (int _d = std::min(2, d); _d < d + 1; ++_d)
        //由于当前的d值可能被修改过(有可能它的每一个祖先
        //被指向了1),所以从2开始枚举d。
        //(特别地,d = 1时不可能被修改过。)
    {
        for (int j = 0; j < m + 1; ++j) F[j] = 0;
        for (int v = 2; v < n + 1; ++v) if (pre[v] == u)
        for (int j = m; j > -1; --j)
        for (int k = j; k > -1; --k)
            F[j] = std::max(F[j], F[k] + g[v][j - k][_d]);
        //对其各个子树进行多重背包。
        for (int j = 0; j < m + 1; ++j)
            f[u][j][_d] = F[j] + C[u] * K[_d];
    }
    if (d > 1) //d > 1时,将其后继结点修改为1才有意义。
    {
        for (int j = 0; j < m + 1; ++j) F[j] = 0;
        for (int v = 2; v < n + 1; ++v) if (pre[v] == u)
        for (int j = m; j > -1; --j)
        for (int k = j; k > -1; --k)
            F[j] = std::max(F[j], F[k] + g[v][j - k][1]);
        for (int j = 1; j < m + 1; ++j)
            f[u][j][1] = F[j - 1] + C[u] * K[1];
    }
    for (int j = 0; j < m + 1; ++j)
    for (int _d = 0; _d < d; ++_d)
        g[u][j][_d] = std::max(f[u][j][_d + 1], f[u][j][1]);
    return;
}

int main()
{
    freopen("trans.in", "r", stdin);
    freopen("trans.out", "w", stdout);
    scanf("%d%d%lf", &n, &m, K + 1);
    for (int i = 2; i < n + 1; ++i) K[i] = K[i - 1] * K[1];
    for (int i = 1; i < n + 1; ++i) scanf("%d", pre + i);
    for (int i = 1; i < n + 1; ++i) scanf("%lf", C + i);
    for (int ths = pre[1], len = 2; ths - 1; ths = pre[ths], ++len)
    //尝试分别将每一个处在环上的结点的父结点修改成1,
    //然后分别做一次动态规划。
    {
        for (int i = 1; i < n + 1; ++i)
        for (int j = 0; j < m + 1; ++j)
        for (int k = 0; k < n + 1; ++k)
            f[i][j][k] = g[i][j][k] = 0;
        int tmp = pre[ths]; double Now = 0;
        pre[ths] = 1;
        for (int v = 2; v < n + 1; ++v) if (pre[v] == 1) Dp(v, 1);
        for (int j = 0; j < m + 1; ++j) F[j] = 0;
        for (int v = 2; v < n + 1; ++v) if (pre[v] == 1)
        for (int j = m; j > -1; --j)
        for (int k = j; k > -1; --k)
            F[j] = std::max(F[j], F[k] + f[v][j - k][1]);
        //由于1的直接子结点不可能被修改过,
        //所以上面只能为f[v][j - k][1]不能为g[v][j - k][1]。
        for (int j = 0; j < m; ++j) Now = std::max(Now, F[j]);
        if (tmp == 1) Now = std::max(Now, F[m]);
        //若ths的父结点本身就是1,说明剩余的子树可以用m次修改机会。
        ans = std::max(ans, (Now + C[1]) / (1 - K[len]));
        pre[ths] = tmp; //将ths的父结点还原。
    }
    printf("%.2lf\n", ans);
    return 0;
}

  • 0
    点赞
  • 1
    收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论 2

打赏作者

Whjpji

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值