【BZOJ1065】【NOI2008】奥运物流(动态规划)

57 篇文章 0 订阅

题面

BZOJ
洛谷

题解

先不考虑环的情况,于是变成了一棵树。
这样子我们答案的贡献是 ni=1Ci×kdep[i] ∑ i = 1 n C i × k d e p [ i ]
其中 dep d e p 是点的深度
考虑环的影响,显然是 R(1) R ( 1 ) 的贡献沿着环反复加在环上,假设环的长度为 len l e n
那么,答案就是 R(1)i=1Ki×len R ( 1 ) ∑ i = 1 ∞ K i × l e n
等比数列求和一下,所以答案就是

ni=1Ci×Kdep[i]1Klen ∑ i = 1 n C i × K d e p [ i ] 1 − K l e n

这样子就可以得到 10 10 分啦。


我们考虑一下如果一个点更改后继,我们显然要让他的深度最小
既然后继可以随意更改,那么它显然接到 1 1 的后面。
因此,一个点如果修改后继,那么它的后继一定是1
同时,因为 K<1 K < 1 ,所以我们尽可能让环的大小最小。


既然每次修改的结果一定是将后继修改为 1 1
那么,我们可以考虑一下dp
f[i][j] f [ i ] [ j ] 表示 i i 的子树中有j个点的后继修改为 1 1 的子树最大贡献
但是这样子我们似乎没法将i这个点的贡献转移给 1 1
因为我们并不知道i的深度。
所以我们增加一维 f[i][j][k] f [ i ] [ j ] [ k ] 表示以 i i 为根的子树中,j个点的后继修改为 i i
其中i号点到 1 1 的距离是k的最大贡献
所以我们的转移有两种,一种是将 i i 号点的后继修改为1号点
那么,它的所有儿子的深度要么是 1 1 ,也就是后继修改为了1
要么是 2 2 ,也就是没有修改后继
转移就是f[u][i][k]=Cu·K+vmax(f[v][j][1],f[v][j][2])
对于第二维是一个背包问题
如果不修改当前点的后继的话,它的所有儿子的深度要么是当前点加一,要么是 1 1
f[u][i][k]=Cu·Kk+vmax(f[v][j][k+1],f[v][j][1])

但是这样子我们发现环还是对答案有影响
所以我们枚举一下环的长度,从对应的地方把它断开成为一棵树,
这样底下的分母的贡献唯一确定,只需要考虑树上的最大贡献了
此时也就是上面所述的 dp d p 了。

说起来好容易,写起来贼麻烦,我自己写WA到不知道哪里去了,最后照着别人打了一遍

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define MAX 65
vector<int> e[MAX];
int n,m,fa[MAX],S[MAX],top,deg[MAX];
double K,C[MAX],f[MAX][MAX][MAX],ans,pw[MAX],g[MAX][MAX];
int Cir[MAX];
void cmax(double &x,double y){if(y>x)x=y;}
void init()
{
    for(int i=1;i<=n;++i)e[i].clear();
    for(int i=1;i<=n;++i)
        for(int j=0;j<=n;++j)
            for(int k=0;k<=n;++k)f[i][j][k]=-1e18;
}
void predp(int u,int dep)
{
    for(int i=0;i<=e[u].size();++i)
        for(int j=0;j<=n;++j)g[i][j]=-1e18;
    g[0][0]=0;
    for(int i=0;i<e[u].size();++i)
    {
        int v=e[u][i];
        for(int k=0;k<=m;++k)
            for(int l=0;l<=k;++l)
                cmax(g[i+1][k],g[i][l]+f[v][k-l][dep]);
    }
}
void dfs(int u)
{
    for(int i=0;i<e[u].size();++i)dfs(e[u][i]);
    predp(u,2);
    for(int i=0;i<=n;++i)
        for(int j=1;j<=m;++j)
            f[u][j][i]=C[u]*K+g[e[u].size()][j-1];
    for(int i=0;i<=n;++i)
    {
        predp(u,i+1);
        for(int j=0;j<=m;++j)
            cmax(f[u][j][i],g[e[u].size()][j]+C[u]*pw[i]);
    }
}
int main()
{
    scanf("%d%d%lf",&n,&m,&K);pw[0]=1;
    for(int i=1;i<=n;++i)pw[i]=pw[i-1]*K;
    for(int i=1;i<=n;++i)scanf("%d",&fa[i]);
    for(int i=1;i<=n;++i)scanf("%lf",&C[i]);
    for(int i=fa[1],len=2;i!=1;i=fa[i],++len)
    {
        init();
        for(int j=n;j>1;--j)
            if(i!=j)e[fa[j]].push_back(j);
            else e[1].push_back(j);
        dfs(1);
        ans=max(ans,f[1][m-(fa[i]!=1)][0]/(1-pw[len]));
    }
    printf("%.2lf\n",ans);
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值