bzoj1065【Noi2008】奥运物流

题意:http://www.lydsy.com/JudgeOnline/problem.php?id=1065

   给一棵基环树,每个点i的权值=ci+k*∑son[i],修改至多m个点的父亲使1号点权值最大

sol:  首先因为转移的式子形成了一个环,所以1号点的权值需要手推QAQ

   对于一个点,其贡献为ci*k^dep*(1+k^len+k^2len+...),因为每个点更新到1后还要在环上反复更新

   所以R1=(∑(ci*k^dep[i]),(i from 1 to n))/(1-k^len)

   还是没法做啊QAQ,必须要拆环

   枚举环长为len,将1的后继设为断点,就可以只考虑分子啦QwQ,然后对于每个环长做dp取max即可

   对于每次修改,易证将其后继设为1最优

   考虑dp,f[i][j][k]表示以i为根的子树修改了j次,i到1的距离为k,即可得到状态转移方程

   f[i][j][dep]=max {∑ max(f[v][J][dep+1],f[v][J][1])+(c[i]*(k^dep)),v为i儿子,∑J=j;//i不向根连边

           ∑ max(f[v][J][2],f[v][J][1])+(c[i]*k),v为i儿子,∑J=j; }//i向根连边

   P.S.注意特判i=1的情况

   然而这样复杂度依然爆炸QAQ

   观察右面的式子,首先先简化方程,令g[i][j][k]=max(f[i][j][k+1],f[i][j][1])

   则右面的式子可以理解为,对于j,选择一种划分方案,使∑ g[i][J][k],(∑J=j) 最大

   即可用多重背包优化该方程,把g看做物品,cost=J,val=g[i][J][k]

   P.S.并不是很明白为什么用邻接表遍历就GG了QAQ,直接暴力扫&判定才过QAQ

#include<iostream>
#include<algorithm>
#include<cstdio>
#include <cstring>
using namespace std;
const int Mx = 80;
double f[Mx][Mx][Mx],g[Mx][Mx][Mx];
double C[Mx],F[Mx],K[Mx],ans;
int n,m,fa[Mx];
void Dp(int x,int dep)
{
    for(int v=2;v<=n;v++) if(fa[v]==x) Dp(v,dep+1);
    for(int d=min(2,dep);d<=dep;d++)//不修改后继
    {
        memset(F,0,sizeof(F));
        for(int v=2;v<=n;v++)//多重背包优化
            if(fa[v]==x)
                for(int j=m;j>=0;j--)
                    for (int k=j;k>=0;k--)
                        F[j]=max(F[j],F[k]+g[v][j-k][d]);
        for(int j=0;j<=m;j++) f[x][j][d]=F[j]+C[x]*K[d];
    }
    if(dep>1)//将其后继结点修改为1
    {
        memset(F,0,sizeof(F));
        for(int v=2;v<=n;v++)
            if(fa[v]==x)
                for(int j=m;j>=0;j--)
                    for(int k=j;k>=0;k--)
                        F[j]=max(F[j],F[k]+g[v][j-k][1]);
        for(int j=1;j<=m;j++) f[x][j][1]=F[j-1]+C[x]*K[1];
    }
    for(int j=0;j<=m;j++)
        for(int d=0;d<dep;d++)
            g[x][j][d]=max(f[x][j][d+1],f[x][j][1]);
}

int main()
{
    cin>>n>>m>>K[1];
    K[0]=1; for (int i=2;i<=n;i++) K[i]=K[i-1]*K[1];
    for(int i=1;i<=n;i++)
    {
        int x;scanf("%d",&x);
        fa[i]=x;
    }
    for(int i=1;i<=n;i++) cin>>C[i];
    for(int now=fa[1],len=2;now!=1;now=fa[now],len++)//枚举环长 
    {
        memset(f,0,sizeof(f));
        memset(g,0,sizeof(g));
        int tmp=fa[now]; double sum=0; fa[now]=1;//断环 
        for(int v=2;v<=n;v++) if(fa[v]==1) Dp(v,1);
        memset(F,0,sizeof(F));
        for(int v=2;v<=n;v++)//因为1的儿子未修改,所以用f而不是g更新F
            if(fa[v]==1)
                for(int j=m;j>=0;j--)
                    for(int k=j;k>=0;k--)
                        F[j]=max(F[j],F[k]+f[v][j-k][1]);
        for(int j=0;j<m;j++) sum=max(sum,F[j]);
        if(tmp==1) sum=max(sum,F[m]);//若now的fa为1,则断环未修改
        ans=max(ans,(sum+C[1])/(1-K[len]));//更新答案 
        fa[now]=tmp;//还原父结点
    }
    printf("%.2lf\n",ans);
    return 0;
}
posted @ 2017-01-12 11:22  Czarina  阅读(434)  评论(0编辑  收藏  举报