点击打开题目链接
微软公司有着严谨的严格的隶属层次层次:每个员工(除了比尔盖茨以外)有且只有一个上司。
新年的到来使得微软公司的总会计师决定给部分员工予以补助。
担心被指责过于贪婪或偏袒员工(因为他只给部分员工予以补助),总会计师研制了以下规则:
1、每个员工都可以指定给予他的下属补助和接受来自他上司的指定。
2、没有员工能同时指定给予他的下属补助和接受来自他上司的指定。
3、没有员工能指定给予他的多名下属补助。
注:这里的指定给予下属补助是说给予下属去公司领取一份补助的权利,并不是将自己拥有的补助给予下属。
方案看起来是完美的,因为为了保留“接受其上司补助”的权利,没有人会指定给予他的下属补助。
员工们不知何故发现了总会计师的计划,他们决定联合起来已得到最多的补助然后平分。
编程求解可以得到的最大总补助,并输出得到最大总补助的一种方案(即哪些员工得到了补助)。
员工获得补助的限制条件(即规则2和规则3)综合即得:一个员工(用X表示)和他的所有下属中只能有一个人获得补助。因为:
1、若X获得了补助,必定是接受了其上司的指定,于是X不能再指定补助给予他的下属,此时获得补助的只有X。
2、若X的一名下属获得了补助,必定是接受了X的指定,于是X既不能接受其上司的指定也不能再指定其他下属,此时获得补助的只有
一名下属。
所以题目可抽象为:在一棵有根数上(在微软公司员工隶属关系树上),
选取尽可能多的节点(选择尽可能多的员工),
满足每个节点和他的子节点中(满足每个员工和他的所有下属中),
只有一个节点被选取(只有一个人获得补助)。
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
一、树形DP
1、状态转移:
用F[i][0]表示不选第i个节点,F[i][1]表示选取第i个节点,则:
F[i][0]=∑F[son of i][0]-MIN(F[the son of i][0]-F[the son of i][1])。
F[i][1]=∑F[son of i][0]+1。
2、深度搜索:
由于每个节点的父节点唯一且在深度搜索过程中父节点仅会调用每个子节点的数据一次,因此深度搜索不需要增添记忆化搜索语句。
3、方案输出(最为麻烦):
a.在深度搜索过程中记录在不选取当前节点cur时选取的子节点son(令BestSon[cur]=son)。
b.初始化数组ChooseHash为false。
c.遍历所有节点,若当前节点cur的父节点未被选取且其为父节点的最佳子节点时,标记cur会被选取(令ChooseHash[cur]=true)。
d.扫描数组ChooseHash,输出值为true的节点编号。
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
二、贪心
首先考虑:应不应该选取最底层的节点?(下图中A为最底层的节点,B为A的父节点,红色标记已被选取的节点,灰色标记不可选
取的节点)
分类讨论:1、不选取B子树的任何节点,则B子树中选取的节点数为0,此时不对B子树外任何节点的选取造成影响。
2、选取B子树的叶节点(以A为例),则B子树中选取的节点数为1,此时不对B子树外的任何节点的选取造成影响(图
中左树所示)。
3、选取B子树的根节点(即B节点),则B子树中选取的节点数为1,此时会使B子树外的B节点的兄弟节点和父亲节点无
法被选取(图中右树所示)。
比较可知:第三种选择为最佳选择,即最底层的叶节点必须被选取。
根据题意:员工的编号大于他上司的编号(原文在Input中:each programmer has the number greater then the number of
his chief)。
于是又有:最底层的节点即编号最大的节点。
那么便可:1、选取父节点以及自身未被标记的节点中编号最大的节点:
通过父节点是否被标记可知兄弟节点是否被选取,通过自身是否被标记可知子节点是否被选取。
2、标记选取节点的父节点。
3、重复上述步骤直到没有节点可供选取为止。
一、树形DP
#include<stdio.h> #include<string.h> const int MAXN=500005; const int INF4=0x1f1f1f1f; int N,F[MAXN][2],SubordListHead[MAXN],BestSon[MAXN],MemTot; bool ChooseHash[MAXN]; struct Node { int ID,Next; } NodeMem[MAXN]; void TreeDP(int CurID) { int sum=0,min=INF4; for(int son=SubordListHead[CurID];son!=-1;son=NodeMem[son].Next) { int sonid=NodeMem[son].ID; TreeDP(sonid); //计算子节点数据 sum+=F[sonid][0]; //sum=∑F[son of CurID][0] if(F[sonid][0]-F[sonid][1]<min) { BestSon[CurID]=sonid; //更新最佳子节点 min=F[sonid][0]-F[sonid][1]; //min=MIN(F[son of CurID][0]-F[son of CurID][1]) } } F[CurID][0]=(min==INF4?0:sum-min); //F[i][0]=∑F[son of CurID][0]-MIN(F[son of CurID][0]-F[son of CurID][1]) F[CurID][1]=sum+1; //F[i][1]=∑F[son of CurID][0]+1 } void GetBestScheme(int CurID,bool CurIsChoose) { ChooseHash[CurID]=CurIsChoose; for(int son=SubordListHead[CurID];son!=-1;son=NodeMem[son].Next) { //只有未被选择的父节点的最佳子节点才会被选择 bool SonNeedChoose=(!CurIsChoose&&NodeMem[son].ID==BestSon[CurID]); GetBestScheme(NodeMem[son].ID,SonNeedChoose); } } void OutBestScheme() { memset(ChooseHash,0,sizeof(ChooseHash)); TreeDP(1); GetBestScheme(1,false); printf("%d\n",F[1][0]*1000); for(int i=2;F[1][0];++i) { if(ChooseHash[i]) { printf("%d%c",i,--F[1][0]?' ':'\n'); } } } void BuildTree() { MemTot=0; memset(SubordListHead,-1,sizeof(SubordListHead)); for(int i=1;i<=N-1;++i) { int ChiefID; scanf("%d",&ChiefID); NodeMem[MemTot].ID=i+1; //将节点接在链表头部 NodeMem[MemTot].Next=SubordListHead[ChiefID]; SubordListHead[ChiefID]=MemTot++; } } int main() { while(scanf("%d",&N)==1) { BuildTree(); OutBestScheme(); } return 0; }
二、贪心
#include<stdio.h> #include<string.h> int N,Father[500005],Ans[500005]; bool Unelectable[500005]; int main() { while(scanf("%d",&N)==1) { for(int i=2;i<=N;++i) { scanf("%d",&Father[i]); } memset(Unelectable,0,sizeof(Unelectable)); int sum=0; for(int i=N;i>=2;--i) { if(!Unelectable[i]&&!Unelectable[Father[i]]) { Unelectable[Father[i]]=1; Ans[sum++]=i; } } printf("%d\n",sum*1000); for(int i=sum-1;i>=0;--i) { printf("%d%c",Ans[i],i?' ':'\n'); } } return 0; }