Query on a tree V (动态点分治预处理,LCA计算两点距离, 堆维护最短距离)

博客围绕Query on a tree V问题展开,给出一棵N结点树,边权为1,初始点为黑色,有Q个询问,包含改变点颜色和询问最近白点距离两种操作。题解使用动态点分治和lca,将复杂度降至log(n),还介绍了堆维护和查询计算方法,并附上AC代码。

Query on a tree V 

You are given a tree (an acyclic undirected connected graph) with N nodes. The tree nodes are numbered from 1 to N. We define dist(a, b) as the number of edges on the path from node a to node b. 

Each node has a color, white or black. All the nodes are black initially. 

We will ask you to perfrom some instructions of the following form: 

  • 0 i : change the color of i-th node(from black to white, or from white to black). 
  • v : ask for the minimum dist(u, v), node u must be white(u can be equal to v). Obviously, as long as node v is white, the result will always be 0. 

Input

  • In the first line there is an integer N (N <= 100000) 
  • In the next N-1 lines, the i-th line describes the i-th edge: a line with two integers a b denotes an edge between a and b. 
  • In the next line, there is an integer Q denotes the number of instructions (Q <= 100000) 
  • In the next Q lines, each line contains an instruction "0 i" or "1 v

Output

For each "1 v" operation, print one integer representing its result. If there is no white node in the tree, you should write "-1". 

Example

Input:
10
1 2
1 3
2 4
1 5
1 6
4 7
7 8
5 9
1 10
10
0 6
0 6
0 6
1 3
0 1
0 1
1 3
1 10
1 4
1 6

Output:
2
2
2
3
0

题意: 给出一棵n结点的树,边权为1,一开始每个点颜色都是黑色。
现有q个询问,每次询问会有两种操作:
1.0 i 改变i的颜色,黑变白,白变黑。
2.1 v 询问与v距离最近的白点,显然,当v颜色为白色时,答案是0。

题解:其实该题就是一个模板题,前置技能:动态点分治和lca

先用动态点分治存储每颗子树的重心,目的是将后面的查询与修改复杂度降到 log(n)

首先我们得为每一个点创建一个堆,我们知道每个点都是一颗子树的重心,我们找到该点为重心的子树中的所有白点,用重心对应的堆来维护这些白点到重心的距离最小值

操作0:在修改过程中,当遇到一个白点的时候,我们找到存在该点的所有子树(点分治分离出来的子树)的重心,将每个重心到该白点的距离丢到该重心对应堆里

void op0(int v){
    col[v] = !col[v]; // 修改颜色
    if(col[v]) k ++;  // k表示白色点的数目
    else k --;
    if(col[v]) { // 如果修改为白色后,我们就要计算重心到该点的距离并堆维护
        int cent = v; // cent表示重心
        while(cent != -1) {
            int comfa = lca(cent, v); // comfa 表示公共祖先
            qu[cent].push ( len(v, dep[cent] + dep[v] - 2 * dep[comfa]) ); // 堆维护
            cent = pre[cent]; // 递归到上一个重心
        }
    }
}

操作1:在查询过程中,我们同样的找到存在该点的所有子树(点分治分离出来的子树)的重心,我们将该点到重心的距离算出来,再加上距离该重心最近的白点的距离。针对以上找到的每一个重心,我们做一次以上计算,取其中的最小值输出。当然要先特判白点的个数是否为0或者该点本身是否为白点

int op1(int v){
    if(k == 0) return -1; // 特判
    if(col[v]) return 0;
    int ans = INF;
    int cent = v; // cent表示重心
    while(cent != -1) {
        int comfa = lca(v, cent);  // comfa 表示公共祖先
        int dis = get_dis(cent);  // dis 表示到cent最近的白点的距离
        ans = min(ans, dep[cent] + dep[v] - 2 * dep[comfa] + dis); 
        cent = pre[cent]; // 递归到上一个重心
    }
    return ans;
}

关于堆维与get_dis() 函数,如果要维护的点的颜色时候已经修改为黑色,我们需要把它删除,所以我们在维护距离的时候同时要存下点的位置

struct len { int to, dis;
    len (int x, int y) : to(x), dis(y) {};
    bool friend operator < (len a, len b) { return a.dis > b.dis; }
    // 维护最小的dis
};

priority_queue< len >qu[maxn];

int get_dis(int u) {
    while(!qu[u].empty() && !col[ qu[u].top().to]) qu[u].pop(); 
    // 若已经修改为黑色需要pop一下
    return qu[u].empty() ? INF : qu[u].top().dis;
}

下面附上AC代码 

//#include<bits/stdc++.h>
//#include<unordered_map>
//#include<unordered_set>
#include<iostream>
#include<sstream>
#include<iterator>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<set>
#include<vector>
#include<bitset>
#include<climits>
#include<queue>
#include<iomanip>
#include<cmath>
#include<stack>
#include<map>
#include<ctime>
#include<new>
using namespace std;
#define LL long long
#define ULL unsigned long long
#define MT(a, b) memset(a,b,sizeof(a))
const int INF  =  0x3f3f3f3f;
const int O    =  1e6;
const int mod  =  1e6+7;
const int maxn =  4e5 +5;
const double PI  =  acos(-1.0);
const double E   =  2.718281828459;
const double eps = 1e-8;

struct dd { int to, next;
    dd () {};
    dd (int x, int y)  : to(x), next(y) {};
}e[maxn];
int head[maxn], cnt = 0;
void add(int u, int v) { e[cnt] = dd (v, head[u]); head[u] = cnt ++; }

int n, k = 0;


// 点分

int root, max_son[maxn], sum[maxn], vis[maxn], pre[maxn];

void get_root(int u, int fa, int num) {
    sum[u] = 1; max_son[u] = 0;
    for(int i=head[u]; i!=-1; i=e[i].next) {
        int v = e[i].to;
        if(v == fa || vis[v]) continue;
        get_root(v, u, num);
        sum[u] += sum[v];
        max_son[u] = max(max_son[u], sum[v]);
    }
    max_son[u] = max(max_son[u], num - sum[u]);
    if(max_son[u] < max_son[root]) root = u;
}

void fun(int u, int f) {
    pre[u] = f; vis[root] = 1;
    for(int i=head[root]; i!=-1; i=e[i].next) {
        int v = e[i].to;
        if(vis[v]) continue;
        max_son[root = 0] = INF; get_root(v, u, sum[v]);
        fun(root, u);
    }
}


// LCA

int dep[maxn], up[maxn][21];

void dfs(int u,int fa,int d) {
    dep[u] = d + 1;
    for(int i = 1 ; i < 20 ; i ++) {
        up[u][i] = up[up[u][i-1]][i-1];
    }
    for(int i = head[u] ; i!=-1 ; i = e[i].next) {
        int v = e[i].to;
        if(v == fa) continue;
        up[v][0] = u;
        dfs(v, u, d+1);
    }
}
int lca(int u,int v) {
    int mx = 0;
    if(dep[u] < dep[v]) swap(u,v);
    int k = dep[u] - dep[v];
    for(int i = 19 ; i >= 0 ; i --) {
        if((1<<i) & k) {
            u = up[u][i];
        }
    }
    if(u == v) return u;
    for(int i = 19 ; i >= 0 ; i --) {
        if(up[u][i] != up[v][i]){
            u = up[u][i];
            v = up[v][i];
        }
    }
    return up[u][0];
}


// 堆维

int col[maxn];

struct len { int to, dis;
    len (int x, int y) : to(x), dis(y) {};
    bool friend operator < (len a, len b) { return a.dis > b.dis; }
};

priority_queue< len >qu[maxn];

int get_dis(int u) {
    while(!qu[u].empty() && !col[ qu[u].top().to]) qu[u].pop();
    return qu[u].empty() ? INF : qu[u].top().dis;
}

void op0(int v){
    col[v] = !col[v];
    if(col[v]) k ++;
    else k --;
    if(col[v]) {
        int cent = v;
        while(cent != -1) {
            int comfa = lca(cent, v);
            qu[cent].push ( len(v, dep[cent] + dep[v] - 2 * dep[comfa]) );
            cent = pre[cent];
        }
    }
}

int op1(int v){
    if(k == 0) return -1;
    if(col[v]) return 0;
    int ans = INF;
    int cent = v;
    while(cent != -1) {
        int comfa = lca(v, cent);
        int dis = get_dis(cent);
        ans = min(ans, dep[cent] + dep[v] - 2 * dep[comfa] + dis);
        cent = pre[cent];
    }
    return ans;
}

int main(){
    scanf("%d", &n);
    MT(head, -1);
    for(int i=1; i<n; i++) {
        int u, v; scanf("%d%d", &u, &v);
        add(u, v); add(v, u);
    }
    dfs(1, -1, 0);
    max_son[root = 0] = INF; get_root(1, -1, n);
    fun(root, -1);
    int q; scanf("%d", &q);
    while( q --) {
        int c, v; scanf("%d%d", &c, &v);
        if(c) printf("%d\n", op1(v));
        else op0(v);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值