Description
很久很久之前,森林里住着一群兔子。有一天,兔子们突然决定要去看樱花。兔子们所在森林里的樱花树很特殊。樱花树由n个树枝分叉点组成,编号从0到n-1,这n个分叉点由n-1个树枝连接,我们可以把它看成一个有根树结构,其中0号节点是根节点。这个树的每个节点上都会有一些樱花,其中第i个节点有c_i朵樱花。樱花树的每一个节点都有最大的载重m,对于每一个节点i,它的儿子节点的个数和i节点上樱花个数之和不能超过m,即son(i) + c_i <= m,其中son(i)表示i的儿子的个数,如果i为叶子节点,则son(i) = 0
Input
第一行输入两个正整数,n和m分别表示节点个数和最大载重
Output
一行一个整数,表示最多能删除多少节点。
Sample Input
0 2 2 2 4 1 0 4 1 1
3 6 2 3
1 9
1 8
1 1
0
0
2 7 4
0
1 5
0
Sample Output
HINT
对于100%的数据,1 <= n <= 2000000, 1 <= m <= 100000, 0 <= c_i <= 1000
题解Here!
额,刚拿到手自然没有什么思路。。。
我们自底向上,贪心的优先删除每个点的儿子中代价最小的一个。
即:以$son[i]+val[i]$为$i$点的代价,每个点我们选取代价最小的删除,结果一定不会变差。
证明?不存在的如下:
首先对于$i$点和$i$的两个儿子$p,q$,由决策包容性我们可以知道:
如果$val[p]+son[p]<val[q]+son[q]$,选$p$优先删除一定比选$q$优先删除更优。
所以我们从根节点$dfs$,从叶子结点向上回溯。
路上如果遇到能删除的点就删,不必考虑其祖先。
这个证明。。。$emmm.... $
证明:设点$i$的儿子是$p$,$p$的兄弟是$q$,$j$还有一个儿子是$j$。
$dfs$的过程中,如果在回溯到$p$的时候发现可以删除$j$,那么就删除$j$,并更新$p$本身的代价。
这样可能会导致无法再回溯到$i$点的时候删除$q$。
等一下,这不是有后效性嘛。
但是因为贪心删了儿子而导致这个点不能再删,那么我们只会损失一个点,就是该点。
而删除儿子至少会删除一个,所以不会亏。。。
所以,自底向上的删除无后效性,满足贪心性质。
然后就可以愉快地$dfs$了。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #define MAXN 2000010 using namespace std; int n,m,c=1,ans=0; int val[MAXN],head[MAXN],son[MAXN],num[MAXN],Left[MAXN],Right[MAXN]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } inline bool cmp(const int &p,const int &q){ return val[p]+son[p]<val[q]+son[q]; } void dfs(int rt){ if(!son[rt])return; for(int i=Left[rt];i<=Right[rt];i++)dfs(num[i]); sort(num+Left[rt],num+Right[rt]+1,cmp); for(int i=Left[rt];i<=Right[rt];i++){ if(son[num[i]]+val[num[i]]+son[rt]+val[rt]-1<=m){ ans++; val[rt]+=val[num[i]]; son[rt]+=son[num[i]]-1; } else break; } } void work(){ dfs(1); printf("%d ",ans); } void init(){ int x,y; n=read();m=read(); for(int i=1;i<=n;i++)val[i]=read(); for(int i=1;i<=n;i++){ x=son[i]=read(); Left[i]=c; while(x--)num[c++]=read()+1; Right[i]=c-1; } } int main(){ init(); work(); return 0; }