【BZOJ1103】大都市 解题报告

题目传送门
打算5分钟写完题解

题目大意

有一棵n个点的有根树, 初始时每条边均为红色,有两种操作:

  1. 把某条边染为蓝色
  2. 统计根到某一点路径上的红边数量

思路

  1. \(a_i\)表示根到点i路径上的红边数,修改边(u,v)时,不失一般性,设\(dep(u) < dep(v)\),那么以v为根的子树中,所有\(a_i\)都要减1。用线段树点查区改
  2. 用黑科技欧拉序,不失一般性,设\(dep(u) < dep(v)\),把边(u,v)的颜色下放到点v,线段树/树状数组点改区查即可。

关于欧拉序:算法导论中的DFS序好像就叫欧拉序,它有一个优越的性质:根到任意一点的路径对应欧拉序上的一段前缀区间

易错点

  • 树状数组维护的范围是\([1,2n]\),而不是\([1,n]\)
  • 无向边数组开2倍

代码

#include<iostream>
#include<cstdio>
using namespace std;

const int maxn = 250007;
int n, m, C[maxn << 1];
int stamp, dep[maxn], tid[maxn], tif[maxn];
bool vis[maxn];
int edgenum, head[maxn], Next[maxn << 1], vet[maxn << 1];

inline void addedge(int u, int v){
    ++edgenum;
    vet[edgenum] = v;
    Next[edgenum] = head[u];
    head[u] = edgenum;
}

void DFS(int u, int D){
    //printf("%d\n", u);
    vis[u] = true; tid[u] = ++stamp; dep[u] = D;
    for (int e = head[u]; e; e = Next[e]){
        int v = vet[e];
        if (!vis[v]) DFS(v, D+1);
    }
    tif[u] = ++stamp;
}

inline void add(int i, int val){
    for (; i <= n+n; i += (i & (-i)))
        C[i] += val;
}

inline int sum(int i){
    int res = 0;
    for (; i >= 1; i -= (i & (-i)))
        res += C[i];
    return res;
}

int main(){
    scanf("%d", &n);
    for (int i = 1; i < n; ++i){
        int u, v;
        scanf("%d%d", &u, &v);
        addedge(u,v);
        addedge(v,u);
    }
    DFS(1, 1); 
    for (int i = 2; i <= n; ++i){
        add(tid[i], +1);
        add(tif[i], -1); 
    }
    scanf("%d", &m);
    for (int i = 1; i <= n + m - 1; ++i){
        char T[2];
        scanf("%s", T);
        if (T[0] == 'A'){
            int u,v;
            scanf("%d%d", &u, &v);
            if (dep[u] > dep[v]) swap(u, v);
            add(tid[v], -1); add(tif[v], +1);
        }else{
            int a;
            scanf("%d", &a);
            printf("%d\n", sum(tid[a]));
        }
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/YJZoier/p/9432758.html