题意: 给你一棵以1为根的树,让你将这棵树黑白染色,再给每个点赋一个权值,要求对于每个点,它和它子树中同样颜色的点权值之和要恰好等于x[v],问是否可行。 Miss U
首先我们可以假设点 now 为黑色,如果是白色的话,我们后面的答案稍微换一下即可了。
接着如果我们考虑一个点的子树的时候,如果已经知道了它的颜色(假如是黑色),我们只关心在该情况下白色的权值和最小值,而且只要某个点的子树黑色的权值和能达到一个小于等于x[now]的值说明这个状态合法。
由于不关心在黑色的权值和确定的情况下,我们只关心白色的权值和最小值,那么我们就很容易得到一个dp状态了。dp[i][j]表示考虑到第i个点,它子树的黑色权值和为j的情况下白色点权值和的最小值,并将dp初值设为inf。
考虑转移的时候,我们就可以分开讨论某个子节点 k 是黑色还是白色,
如果子节点k是黑色,那么有dp[i][j+w[k]]=min(dp[i][j+w[k]],dp[i][j]+min(dp[k][0~w[k]]),即为给i的子树增加w[k]的黑色节点,为什么是min(dp[k][0~w[k]])呢,因为我并不需要k的子树里有w[k]个节点,只要有小于等于w[k]个,我给k这个节点增加差的权值就可以了。
因为我们这时候只关心dp[k][0~w[k]]的最小值,预处理一下就可以了,我们令它为 x,
同理,如果子节点k是白色,那么有dp[i][j+x]=min(dp[i][j+x],dp[i][j]+w[k]),由于黑色和白色是互逆的,所以这时候子节点k的dp的意义就变成了节点k染色为白色时,黑色的最小个数,便得到了如上的dp方程。
这样从下向上dp,最后只要dp[1][0~w[1]]有一个dp值不为inf了,就说明有合法的转移能转移上来了,就说明有解,否则无解。
注意treedp的时候防止一个子树多次更新dp值,可以每次先复制上一个子树的答案,再把dp数组置为inf再进行更新,(其实似乎反着枚举当背包就行了)
下附AC代码。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<stdlib.h>
#define maxn 1005
using namespace std;
int n,tot;
int head[maxn],nex[maxn],to[maxn],v[maxn];
void add(int x,int y)
{
to[++tot]=y; nex[tot]=head[x]; head[x]=tot;
}
int dp[maxn][maxn*5],temp[maxn*5];
int dfs(int now)
{
dp[now][0]=0;
for(int i=head[now];i;i=nex[i])
{
int w=dfs(to[i]),b=v[to[i]];
memcpy(temp,dp[now],sizeof(temp));
memset(dp[now],0x3f,sizeof(dp[now]));
for(int j=0;j<=v[now];j++)
{
if(j+w<=v[now])
dp[now][j+w]=min(dp[now][j+w],temp[j]+b);
if(j+b<=v[now])
dp[now][j+b]=min(dp[now][j+b],temp[j]+w);
}
}
int ans=dp[0][0];
for(int i=0;i<=v[now];i++)
ans=min(ans,dp[now][i]);
// cerr<<now<<" "<<ans<<endl;
return ans;
}
int main()
{
memset(dp,0x3f,sizeof(dp));
scanf("%d",&n);
for(int i=2;i<=n;i++)
{
int x;
scanf("%d",&x);
add(x,i);
}
for(int i=1;i<=n;i++)
scanf("%d",&v[i]);
int res=dfs(1);
// cout<<res<<endl;
if(res<dp[0][0])
return printf("POSSIBLE\n"),0;
printf("IMPOSSIBLE\n");
}
我就想让某个傻子来试这里有没有彩蛋 Especially For U
By ZRX