题目描述~
简化一下题意就是:
给定一颗基环树(n个点,n条边),设每个点的权值为w[i] 则 w[i]=c[i]+k*sigma(w[son[i]]),最多能修改m个点,使得w[1]最大
QWQ一开始看这个题,没啥头绪
只是有一些奇怪的想法:
1.如果修改,一定是将一个点的后继修改到1
2.每一个点的贡献.....应该是可以推式子吧~
带着这两个想法,我开始研究论文。
首先,考虑到根是一个环,所以一个点对1的贡献一定是会不断迭代转移的
我们可以手推一下式子:
这样我们就得出了每个点对于R(1)的贡献
同时,我们也能得到一个性质就是,只要我们固定下了环长,那么只需要让分子最大,R(1)也就最大了
所以,我们枚举环的长度,即修改环上某个点的,这样1-klen确定,只需分子尽可能大。现在我们有m次修改机会,
根据公式,显然每次修改都应该把修改点的后继直接设置为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向根连边
枚举长度len,以1的后继开始,依次作为断点,直到重新回到1为止
而对于每一个断点,因为环已经确定,所以不能再修改环上点的后继,只能改变一个非环上点的后继。
可是可是QWQ就算是想出了这样的dp转移方程,TLE也是在所难免的
QAQ然后,我们可以发现,在转移的时候,对于f[i][j][dep]无非是求了一个f[son[i]][j][dep+1]和f[son[i]][j][1]的max
所以,我们令g[i][j][dep]=max(g[i][j][dep+1],g[i][j][1]);
则上面的式子可以理解为,对于j,选择一种划分方案,使∑ g[i][J][k],(∑J=j) 最大
即可用01背包优化该方程,令总体积为j,然后有 son个物品,每一个物品的价值是g[son[i]][p][k],体积是p,要求正好装满背包的最大收益
QAQ而在背包的时候,我们可以用ff[i][j]表示以i为根的子树修改了j次的最大收益来统计结果和更新答案。
接着,我们来对着代码来进一步理解:
首先我们看一个主要的dp部分(树形dp)
void dp(int x,int dep)
{
for (int i=2;i<=n;i++) if (fa[i]==x) dp(i,dep+1);
for (int d=min(2,dep);d<=dep;d++)
{
memset(ff,0,sizeof(ff));
for (int son=2;son<=n;son++)
{
if (fa[son]==x)
{
for (int j=m;j>=0;j--)
for (int k=j;k>=0;k--)
ff[j]=max(ff[j],ff[k]+g[son][j-k][d]); //01背包QWQ感觉题解错了
}
}
for (int j=0;j<=m;j++) f[x][j][d]=ff[j]+c[x]*k[d];
}
if (dep>1)
{
memset(ff,0,sizeof(ff));
for (int son=2;son<=n;son++)
{
if (fa[son]==x)
{
for (int j=m;j>=0;j--)
for (int k=j;k>=0;k--)
ff[j]=max(ff[j],ff[k]+g[son][j-k][1]); //01背包QWQ感觉题解错了
}
}
for (int j=1;j<=m;j++) f[x][j][1]=ff[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]);
}
首先,树形dp的经典套路,先dfs到底,再进行计算:
for (int i=2;i<=n;i++) if (fa[i]==x) dp(i,dep+1);
对于任何一个点,都可以不修改他的后继
也就是直接由他的儿子修改或者不修改转移
因为考虑到他的儿子可能会被修改,导致他的深度枚举他的深度,
for (int d=min(2,dep);d<=dep;d++)
{
memset(ff,0,sizeof(ff));
for (int son=2;son<=n;son++)
{
if (fa[son]==x)
{
for (int j=m;j>=0;j--)
for (int k=j;k>=0;k--)
ff[j]=max(ff[j],ff[k]+g[son][j-k][d]); //01背包QWQ感觉题解错了
}
}
for (int j=0;j<=m;j++) f[x][j][d]=ff[j]+c[x]*k[d];
}
k数组是预处理的的一个幂次方
因为当前点不修改,所以ff[j]可以到m (这里用的是01背包常用的小技巧,将数组降维)
QAQ需要注意的是,这里千万不要忘记重新用ff数组 和 c数组,更新当前节点的f[x][j][d],以便于之后的g和答案的更新
同时,如果这个点不是dep为1的点,就说明他原本的后继不是1,那么他的后继就可以修改,则:
memset(ff,0,sizeof(ff));
for (int son=2;son<=n;son++)
{
if (fa[son]==x)
{
for (int j=m-1;j>=0;j--)
for (int k=j;k>=0;k--)
ff[j]=max(ff[j],ff[k]+g[son][j-k][1]); //01背包QWQ感觉题解错了
}
}
for (int j=1;j<=m;j++) f[x][j][1]=ff[j-1]+c[x]*k[1];
这时候的对于j 我们需要从m-1开始枚举,因为我们需要修改当前点
同时更新f数组的时候,也是用ff[j-1]来更新咯
最后在dp 的时候,更新一个g数组就OK了
而在主程序
for (int len=2;len;len++)
{
if (now==1) break;
int front = fa[now];
sum=0;
fa[now]=1;
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
for (int son=2;son<=n;son++) if (fa[son]==1) dp(son,1);
memset(ff,0,sizeof(ff));
for (int son=2;son<=n;son++) {
if (fa[son]==1)
for (int j=m;j>=0;j--)
for (int k=j;k>=0;k--)
ff[j]=max(ff[j],ff[k]+f[son][j-k][1]);
}
for (int j=0;j<m;j++) sum=max(sum,ff[j]);
if (front == 1) sum=max(sum,ff[m]);
ans=max(ans,(sum+c[1])/(1-k[len]));
fa[now]=front;
now=fa[now];
}
首先将now复制成fa[1],然后每次将now赋值成fa[now]表示依次将环上的点作为断点
记得初始化f和g
然后从每个与1相连的点开始 dfs进行dp
然后用同样的方法更新ff数组,然后用ff[0]~ff[m-1]更新ans (因为本身断环就需要一次修改)
若front==1 也就是当前点本来就是接在1上的
不需要消耗次数
所以只有front==1的时候,才可以用ff[m]更新
最后把fa[now]复原
QWQ大致就是这样了
上代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
const int maxn = 110;
double f[maxn][maxn][maxn],g[maxn][maxn][maxn],ff[maxn];
double k[maxn];
int n,m,fa[maxn];
double c[maxn];
int now;
double ans,sum;
void dp(int x,int dep)
{
for (int i=2;i<=n;i++) if (fa[i]==x) dp(i,dep+1);
for (int d=min(2,dep);d<=dep;d++)
{
memset(ff,0,sizeof(ff));
for (int son=2;son<=n;son++)
{
if (fa[son]==x)
{
for (int j=m;j>=0;j--)
for (int k=j;k>=0;k--)
ff[j]=max(ff[j],ff[k]+g[son][j-k][d]); //01背包QWQ感觉题解错了
}
}
for (int j=0;j<=m;j++) f[x][j][d]=ff[j]+c[x]*k[d];
}
if (dep>1)
{
memset(ff,0,sizeof(ff));
for (int son=2;son<=n;son++)
{
if (fa[son]==x)
{
for (int j=m-1;j>=0;j--)
for (int k=j;k>=0;k--)
ff[j]=max(ff[j],ff[k]+g[son][j-k][1]); //01背包QWQ感觉题解错了
}
}
for (int j=1;j<=m;j++) f[x][j][1]=ff[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()
{
scanf("%d%d%lf",&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 p;
scanf("%d",&p);
fa[i]=p;
}
now=fa[1];
for (int i=1;i<=n;i++) scanf("%lf",&c[i]);
for (int len=2;len;len++)
{
if (now==1) break;
int front = fa[now];
sum=0;
fa[now]=1;
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
for (int son=2;son<=n;son++) if (fa[son]==1) dp(son,1);
memset(ff,0,sizeof(ff));
for (int son=2;son<=n;son++) {
if (fa[son]==1)
for (int j=m;j>=0;j--)
for (int k=j;k>=0;k--)
ff[j]=max(ff[j],ff[k]+f[son][j-k][1]);
}
for (int j=0;j<m;j++) sum=max(sum,ff[j]);
if (front == 1) sum=max(sum,ff[m]);
ans=max(ans,(sum+c[1])/(1-k[len]));
fa[now]=front;
now=fa[now];
}
printf("%.2lf\n",ans);
return 0;
}