• https://blog.csdn.net/u013534123/article/details/78523559
  •  题意
    • 给你一棵树,然后让你对树上的节点进行黑白染色
    • 染色有一些要求:
      • 对于A类要求,要求在x的子树中,至少有y个节点被染成了黑色
      • 对于B类要求,要求在树的所有节点除了x以及其子树节点外,至少有y个节点被染成了黑色
    •  初始状态树是白色的,现在问你,最少给多少个节点染成黑色之后,能够满足所有的A、B类要求。
  • 题解
    • 这种至多至少,然后询问最少的题目,应该就要想到上下界的判断了
    • 我们二分最后的染成黑色的节点数目,然后判定这个答案是否可行。
    • 我们发现
      • 对于A类要求,相当于直接对节点x设置了一个下界,表示以他为根的子树含有的黑色节点数目的下界
      • 但是对于B类要求似乎不太好处理。这个时候可以利用转化的思想。在非x以及其子树的节点中至少包含y个黑色节点,那么等价于在x以及其子树的节点中至多包含n-y个黑色节点,由此,我们就可以以此为依据确定一个上界
    •  这样的话,我们通过dfs,累加每一个儿子节点的上下界,再依此不断缩小上下界。
    • 判定在过程中是否有出现上下界不合法的情况,以及我们枚举的答案是否包含在根的上下界之中,来确定当前枚举的答案的正确与否。
    • 具体实现过程中,我们要注意对于同一个点的要求可能会有多个,不能够使其相互覆盖,而是应该保留特定的要求
  • 代码
    • 
      
      //1 define
      //2 注释在输出的字符中
      //3 函数的判断
      //4 分行符号
      #include<algorithm>
      #include <iostream>
      #include  <fstream>
      #include  <cstdlib>
      #include  <cstring>
      #include  <cassert>
      #include   <cstdio>
      #include   <vector>
      #include   <string>
      #include    <cmath>
      #include    <queue>
      #include    <stack>
      #include      <set>
      #include      <map>
      using namespace std;
      #define P(a,b,c) make_pair(a,make_pair(b,c))
      #define rep(i,a,n) for (int i=a;i<=n;i++)
      #define per(i,a,n) for (int i=n;i>=a;i--)
      #define CLR(vis) memset(vis,0,sizeof(vis))
      #define MST(vis,pos) memset(vis,pos,sizeof(vis))
      #define pb push_back
      //#define mp make_pair
      #define all(x) (x).begin(),(x).end()
      #define fi first
      #define se second
      #define SZ(x) ((int)(x).size())
      typedef pair<int,pair<int,int> >pii;
      typedef long long ll;
      typedef unsigned long long ull;
      const ll mod = 1000000007;
      const int INF = 0x3f3f3f3f;
      ll gcd(ll a, ll b) {
      	return b ? gcd(b, a%b) : a;
      }
      template<class T>inline void gmax(T &A, T B) {
      	(A<B)&&(A=B);//if(B>A)A=B;
      }
      template<class T>inline void gmin(T &A, T B) {
      	(A>B)&&(A=B);//if(B<A)A=B;
      }
      template <class T>
      inline bool scan(T &ret) {
      	char c;
      	int sgn;
      	if(c=getchar(),c==EOF) return 0; //EOF
      	while(c!='-'&&(c<'0'||c>'9')) c=getchar();
      	sgn=(c=='-')?-1:1;
      	ret=(c=='-')?0:(c-'0');
      	while(c=getchar(),c>='0'&&c<='9') ret=ret*10+(c-'0');
      	ret*=sgn;
      	return 1;
      }
      inline void outt(int x) {
      	if(x>9) outt(x/10);
      	putchar(x%10+'0');
      }
      
      const int N = 1e5+10;
      
      int n,up[N],dw[N],d[N],u[N],b[N],sz[N];
      
      int head[N],ver[N*2],nxt[N*2],tot;
      
      void add(int x,int y) {
      	ver[++tot] = y;
      	nxt[tot] = head[x];
      	head[x] = tot;
      }
      
      void init() {
      	tot = 0;
      	CLR(head);
      	CLR(b);
      	CLR(dw);
      }
      
      bool dfs(int x,int fa) {
      	int down=0,UP=1;
      	for(int i=head[x]; i; i=nxt[i]) {
      		int y=ver[i];
      		if (y==fa) continue;
      		if (!dfs(y,x)) return 0;
      		down+=d[y];
      		UP+=u[y];					//累加所有儿子的上下界
      	}
      	d[x]=max(dw[x],down);						//缩小上下界范围
      	u[x]=min(up[x],UP);
      	return d[x]<=u[x];							//如果矛盾,则当前枚举答案不可行
      }
      
      
      //求每个块的大小 
      void getsize(int x,int fa) {
      	sz[x]=1;
      	for(int i=head[x]; i; i=nxt[i]) {
      		int y=ver[i];
      		if (y==fa) continue;
      		getsize(y,x);
      		sz[x]+=sz[y];
      	}
      }
      
      bool check(int x) {
      	for(int i=1; i<=n; i++) {
      		up[i]=min(x-b[i],sz[i]);
      		if (up[i]<0) return 0;  //x是二分产生,不保证x>=b[i] 
      	}
      	return dfs(1,0)&&u[1]>=x&&d[1]<=x;					//最后要求枚举的答案,在根的上下界范围之内
      }
      
      int main() {
      	int T_T;
      	cin>>T_T;
      	while(T_T--) {
      		init();
      		scanf("%d\n",&n);
      		for(int i=1; i<n; i++) {
      			int u,v;
      			scanf("%d%d",&u,&v);
      			add(u,v);
      			add(v,u);
      		}
      		int m,flag=0;
      		getsize(1,0);
      		scanf("%d",&m);
      		while(m--) {
      			int x,y;
      			scanf("%d%d",&x,&y);
      			dw[x]=max(dw[x],y);
      			if (y>sz[x]) flag=1;//对于一个结点可重复输入,下限取最大
      		}
      		scanf("%d",&m);
      		while(m--) {
      			int x,y;
      			scanf("%d%d",&x,&y);
      			b[x]=max(y,b[x]);	//上限n-y取最小,故y取最大 
      			if (y>n-sz[x]) flag=1;
      		}
      		if (flag) {
      			puts("-1");
      			continue;
      		}
      
      		int l=0,r=n,mid;
      		while(l<r){
      			mid=(l+r)/2;
      			if(check(mid))r=mid;
      			else l = mid+1;
      		}
      		printf("%d\n",l);
      	}
      	return 0;
      }

发表评论

邮箱地址不会被公开。 必填项已用*标注