题目描述~
简化一下题意就是:
给定一颗基环树(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; }