「NOI十联测」深邃

「NOI十联测」深邃

要使得最大的连通块最小,显然先二分答案。

先固定1结点为根。

对于一个果实,显然是先处理子树中未分配的点,再向外延伸。

每个结点记录一个(si[]),表示子树中未分配的点数,若为负数,则绝对值代表可以向外延伸的点数。

对于每一个结点(i):

​ 统计儿子中可以向外延伸的点数的最大值MIN,若该结点本身为果实,也算在内(因为(i)结点只能分到一个联通块,而每一个可以延伸的结点必定会占用(i)结点,故只有(MIN)是有用的)。

​ 统计儿子中未分配的点数S。

​ 若(MIN geq S)(si[x]=-(MIN-S))

​ 否则,(si[x] =S)

最后只要判断(si[x] leq 0)即可。

#include<bits/stdc++.h>
#define rep(q,a,b) for(int q=a,q##_end_=b;q<=q##_end_;++q)
#define dep(q,a,b) for(int q=a,q##_end_=b;q>=q##_end_;--q)
#define mem(a,b) memset(a,b,sizeof a )
#define debug(a) cerr<<#a<<' '<<a<<"___"<<endl
using namespace std;
void in(int &r) {
	static char c;
	r=0;
	while(c=getchar(),!isdigit(c));
	do r=(r<<1)+(r<<3)+(c^48);
	while(c=getchar(),isdigit(c));
}
const int mn=200005;
int head[mn],to[mn<<1],ne[mn<<1],cnt1;
#define link(a,b) link_edge(a,b),link_edge(b,a)
#define link_edge(a,b) to[++cnt1]=b,ne[cnt1]=head[a],head[a]=cnt1
#define travel(x) for(int q(head[x]);q;q=ne[q])
int lim,si[mn];
bool mark[mn];
void dfs(int f,int x){
	si[x]=1;
	int Min=0;
	if(mark[x])Min=-lim;
    travel(x)if(to[q]!=f){
	    dfs(x,to[q]);
	    Min=min(Min,si[to[q]]);
	    if(si[to[q]]>0)si[x]+=si[to[q]];
	}
	if(Min+si[x]<=0)si[x]=si[x]+Min;
}
bool check(int x){
	lim=x;
    dfs(0,1);
    return si[1]<=0;
}
int main(){
	freopen("deep.in","r",stdin);
	freopen("deep.out","w",stdout);
    int n,k,a,b;
    in(n),in(k);
    rep(q,1,n-1)in(a),in(b),link(a,b);
    rep(q,1,k)in(a),mark[a]=1;
    int l=n/k,r=n,ans=-1;
    while(l<=r){
	    int mid=l+r>>1;
	    if(check(mid))ans=mid,r=mid-1;
	    else l=mid+1;
	}
	printf("%d
",ans);
    return 0;
} 
原文地址:https://www.cnblogs.com/klauralee/p/11305433.html