zoj4048 Red Black Tree(二分+LCA)

题意:

一棵n个结点的树上有若干个点是红色。有q个询问,每个询问给出k个点,可以将树上一个非红色点染成红色,使得k个点中与红色点的最大距离最小,输出最小值。根节点是红色。
2 <= n,m <= 1e5, 1 <= q <= 2e5

分析:

搜索的过程中将每个点距离最近红色点的距离和根节点的距离记录下来。
二分答案,然后将k个点中距离红色大于答案的拿出来,求这些点的LCA,判断将这个点染成红色能否使得距离红色点的距离都比答案小。

代码:

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2e5+10;
typedef long long ll;
typedef pair<int,int> P;
int n,m,q,r[MAXN],times,rf[MAXN],dep[MAXN],dp[MAXN][20],p[MAXN],mm[MAXN],v[MAXN],tv[MAXN];
ll d[MAXN],rd[MAXN];
vector<P> G[MAXN];
void dfs(int u,int pre){
    if(r[u] == 1){
        rd[u] = 0;  rf[u] = u;
    }
    dp[++times][0] = u;
    p[u] = times;
    for(int i=0;i<(int)G[u].size();i++){
        int v = G[u][i].first, w = G[u][i].second;
        if(v == pre)    continue;
        dep[v] = dep[u] + 1;
        d[v] = d[u] + w;
        rd[v] = rd[u] + w;
        rf[v] = rf[u];
        dfs(v,u);
        dp[++times][0] = u;
    }
}
void rmq_init(){
    for(int j=1;j<=mm[times];j++){
        for(int i=1;i+(1<<j)-1<=times;i++){
            dp[i][j] = dep[dp[i][j-1]] > dep[dp[i+(1<<(j-1))][j-1]] ? dp[i+(1<<(j-1))][j-1] : dp[i][j-1];
        }
    }
}
int rmq_query(int a,int b){
    if(a > b)    swap(a,b);
    int k = mm[b-a+1];
    return dep[dp[a][k]] < dep[dp[b-(1<<k)+1][k]] ? dp[a][k] : dp[b-(1<<k)+1][k];
}
int lca(int a,int b){
    return rmq_query(p[a],p[b]);
}
int ok(ll mid, int k){
    int top=0;
    for(int i=0;i<k;i++){
        if(rd[v[i]] > mid)  tv[top++] = v[i];
    }
    if(top <= 1)  return 1;   //rather than == !!!
    int f = tv[0];
    for(int i=1;i<top;i++)    f = lca(f,tv[i]);
    for(int i=0;i<top;i++)   if(dep[f] <= dep[rf[tv[i]]]) return 0;
    for(int i=0;i<top;i++)   if(mid < rd[tv[i]] - rd[f]) return 0;
    return 1;
}
int read(){
	char c=getchar();
	while (c>'9'||c<'0') c=getchar();
	int x=0;
	while ('0'<=c && c<='9'){
		x=x*10+c-'0';   c=getchar();
	}
	return x;
}
int main(){
    //ios::sync_with_stdio(false);
    //freopen("1.txt","r",stdin);
    mm[0] = -1;
    for(int i=1;i<MAXN;i++){
        mm[i] = ((i&(i-1)) == 0) ? mm[i-1]+1 : mm[i-1];
    }
    int T = read();
    while(T--){
        n = read(); m = read(); q = read();
        times = 0;
        for(int i=0;i<=n;i++)   G[i].clear(), r[i] = 0;
        for(int i=0;i<m;i++){
            int x = read(); r[x] = 1;
        }
        for(int i=0;i<n-1;i++){
            int a,b,c;
            a = read(); b = read(); c = read();
            G[a].push_back(make_pair(b,c));
            G[b].push_back(make_pair(a,c));
        }
        d[1] = 0;
        dep[1] = 1;
        dfs(1,-1);
        rmq_init();
        while(q--){
            int k;
            ll mx = 0;
            k = read();
            for(int i=0;i<k;i++){
                v[i] = read();
                mx = max(mx,rd[v[i]]);
            }
            ll L = 0, R = mx, ans=0;
            while(L <= R){
                ll mid = L+(R-L)/2;
                if(ok(mid,k)){
                    R = mid - 1;
                    ans = mid;
                }else{
                    L = mid + 1;
                }
            }
            cout << ans <<'\n';
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值