@rebirth1120
2019-08-08T00:17:02.000000Z
字数 2289
阅读 894
dp 解题报告
现有一棵有 个节点的树,根节点为 1,每个节点可以染成黑色或白色。
给出一个长度为 的序列,分别为 ,你可以将每个节点赋一个非负整数值,问是否能使以节点 为根的子树中,与 颜色相同的节点(包括 本身)的权值和等于 ( 中的每一个数都要满足)。
一眼dp
看到这题后马上反应树型dp,然后……就没有然后了……
仔细想一下,其实这题在转移的时候并不用考虑把当前点染成哪种颜色,因为它和它的子节点的关系只有两种:颜色相同,颜色不同。 而两个子树的颜色是不会互相干扰的,所以,我们在dp时只需决定每个子节点的颜色是否与根节点相同,而无需考虑它要选那种颜色(相当于可以使整个子树的颜色翻转)。
那么,我们怎么样才能使节点的赋值情况尽量满足题目的要求(即如何做到最有决策)呢?
先考虑一下什么情况下是没办法满足要求的,
对于一个节点,它的每一个子节点都有两个值(白色和黑色),而对于每一个子节点,它都只能且必须选一个值,由于我们为节点赋的值必须是非负整数,所以如果节点 所有的子节点的两个值中的较小值的和 那么就无法满足题目的要求。
为了避免这种情况出现,我们就要使每个节点的值尽量地小。
设 为节点 的值, 为节点 的子树中(不包括它本身)与它颜色相同的点的权值和,可得出式子 ,我们要使 尽量地小,那么就要使 尽量地接近 ,即 为小于等于 的最大值。
那么,我们的问题就转化为:
有 个数,每个数有两个值 ,每个数必须且只能取一个值,求一个最优方案,使 个数的权值之和小于等于定值 ,且最大。
枚举?……
貌似背包可以解决,但是我不会……
那看看还能不能进一步简化问题。
先想想对每个节点要进行的操作,
对于每一个节点 ,我们先要取它的每个子树的两个权值的较小值之和,看它是否小于等于 ,若满足的话,那我们就可以试着用每一个子树的较大值替换它的较小值,使 尽量接近 ,设子树 的较大值和较小值分别为 ,那么,若用子树 的较大值替换它的较小值,对答案的贡献为 。
我们设 ,并将每棵子树 的 存起来(设为 ,那么我们的问题就转化为了:
有 个数,在这 个数中取任意个数,使它们的和尽量接近 ,
现在dp貌似就可做了,
设 为考虑到第 位(且取第 个数)取了 个数的和的最大值(小于等于 ),转移方程为:
能过。
那么这道题的思路基本上就到位了,实现起来还是挺简单的,简单来说就是 dp 套 dp……
#include<algorithm>#include<iostream>#include<cstring>#include<cstdio>#include<queue>using namespace std;const int N=1000+7;int n,size[N],w[N];int lst[N],nxt[N],to[N],tot;void add(int x,int y){nxt[++tot]=lst[x];to[tot]=y;lst[x]=tot;}void init(){cin>>n;int fa;for(int i=2;i<=n;i++){scanf("%d",&fa);add(fa,i);}for(int i=1;i<=n;i++) scanf("%d",&w[i]);}bool run(int u){int sum=0,cnt=0,box[N]; // box数组一定要定义在函数内,不然会受到下一层递归的影响for(int i=lst[u];i;i=nxt[i]){int v=to[i];if(!run(v)) return 0;size[u]+=size[v];int les=min(w[v],size[v]-w[v]);int lar=max(w[v],size[v]-w[v]);sum+=les;box[++cnt]=lar-les;}if(sum>w[u]){ return 0; }sum=w[u]-sum; int maxn=0;sort(box+1,box+1+cnt);priority_queue<int> h[cnt+7];for(int i=1;i<=cnt;i++){if(box[i]>sum) break;for(int j=i;j>=1;j--){int tmp=0;if(!h[j-1].empty()) tmp=h[j-1].top();while(!h[j-1].empty()&&tmp+box[i]>sum){ h[j-1].pop();tmp=h[j-1].top(); }if(tmp+box[i]>sum) continue;h[j].push(tmp+box[i]);maxn=max(maxn,tmp+box[i]);}}size[u]+=sum-maxn;return 1;}int main(){//freopen("tree.in","r",stdin);init();if(run(1)) printf("POSSIBLE\n");else printf("IMPOSSIBLE\n");return 0;}