【ICPC2019上海站】H - Tree Partition
原题:
翻译:
给你一个点权无根树,要你切掉k-1条边把它切成k个子树,要求切完后子树的点权和的最大值最小
一看最大值最小,那必然是二分
问题转化为给你一个值,问你能否把这个树切成不多于k个子树,且每个子树点权和不超过给的值
贪心1:
叶子一定选
贪心2:
如果某个点的子树全能装得下,那一定一把梭
证明:
假设子树的某个子树(也就是孙树)不选,可以使得子树的爸爸多塞两个兄树进去,那么其实完全可以把子树和爸爸的边切掉,让爸爸带两个兄弟跑路,儿子带着孙子,这样答案是没变的,但是爸爸的负担反而轻了,以后还可能比爸爸拖着除了孙树的所有子孙优
那么现在需要考虑子树不能全装的情况
然后我在这一步了比较久,要往下继续贪心不能光靠猜想,还有必要研究性质
性质:
爸爸要么拖几个儿子的子树,要么自己跑路,如果爸爸跑路,那么儿子都跟祖先没啥关系了
这个很好理解,但是必须要想到
想到这个性质之后,就可以继续贪心
贪心3:
每个爸爸的子树(去掉已经打包带走的点之后的剩余部分)按大小排序,然后从小到大枚举,尽可能和爸爸合并
因为爸爸反正都要选,能拖几个儿子就拖几个儿子,优先拖小儿子的最优性易证
这样这道题就做完了
具体实现还不是很直接,有一些需要仔细想想的地方,在代码里标出来了
代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 using namespace std; 5 using ll=long long int; 6 const ll oo=1000000007; 7 struct edg{int nxt,y;}e[210000]; int lk[110000],ltp=0; 8 void ist(int x,int y){ 9 e[++ltp]=(edg){lk[x],y}; lk[x]=ltp; 10 e[++ltp]=(edg){lk[y],x}; lk[y]=ltp; 11 } 12 int n,m,a[110000]; 13 ll q[110000]; int hd=0; 14 ll f[110000]; 15 ll dfs(int x,int y,ll z){ 16 if(a[x]>z) return oo; //注意此处int会溢出 17 f[x]=a[x]; 18 ll cnt=1; 19 for(int i=lk[x];i;i=e[i].nxt)if(e[i].y!=y){ 20 cnt+=dfs(e[i].y,x,z); //易误点,需要想清楚 21 //q[++hd]=f[e[i].y]; 22 } 23 for(int i=lk[x];i;i=e[i].nxt)if(e[i].y!=y) 24 q[++hd]=f[e[i].y]; 25 sort(q+1,q+hd+1); 26 for(int i=1;i<=hd && f[x]+q[i]<=z;++i){ 27 f[x]+=q[i]; 28 cnt--; //易误点,需要想清楚 29 } 30 for(int i=lk[x];i;i=e[i].nxt)if(e[i].y!=y) 31 hd--; //易错点!!! 32 return cnt; 33 } 34 bool chc(ll x){ 35 return dfs(1,0,x)<=m; 36 } 37 ll bsc(){ 38 ll l=0,r=oo*oo,md; 39 while(l+1<r){ 40 md=(l+r)>>1; 41 (chc(md) ? r : l)=md; 42 } 43 return chc(l) ? l : r; 44 } 45 void prvs(){ 46 for(int i=1;i<=n;++i) lk[i]=0; 47 ltp=0; 48 for(int i=1;i<=n;++i) f[i]=0; 49 } 50 int main(){ 51 int T; cin>>T; 52 for(int t=1;t<=T;++t){ 53 scanf("%d%d",&n,&m); 54 prvs(); 55 int l,r; 56 for(int i=1;i<n;++i){ 57 scanf("%d%d",&l,&r); 58 ist(l,r); 59 } 60 for(int i=1;i<=n;++i) scanf("%d",&a[i]); 61 printf("Case #%d: %lld\n",t,bsc()); 62 } 63 return 0; 64 }