spojCOT2 Count on a tree II

题面在这里

题意:

给一棵树,每个点有点权。
m个询问,每次询问一对点u,v,问u-v这条链上有多少种不同的点权。

做法:

一眼看就是莫队。。
不过是树上的。

扔一个博客:http://vfleaking.blog.163.com/blog/static/174807634201311011201627/
(我自己也没仔细看过但似乎比较好)

首先将树分块。
分块的方法就是按照dfs序来分。
然后将查询里的x,y,按照x所在块为第一关键字,y的dfs序为第二关键字排序。

我们定两个指针l,r(和数列上的莫队一样),初始时指向同一个点。
假设现在要查询的两个点为x,y。
保存一个b[i],表示i这个点是否存在于当前的路径里。
那么我们修改的时候,把l到x路径上除lca(l,x)的点b[]全都取反,把r到y路径上除lca(r,y)的点b[]全都取反。
然后把lca(x,y)的b[i]取反,统计答案,再把lca(x,y)的状态取反。
这样就能保证复杂度是根号的了qaq(??)

ps.这题样例很菜,原先倍增表没有预处理(相当于只保存了直接的父亲),然后居然过了样例!

代码:

/*************************************************************
    Problem: spoj COT2 Count on a tree II
    User: fengyuan
    Language: C++
    Result: Accepted
    Time: 2510 ms
    Memory: 33.8 MB
    Submit_Time: 2018-01-20 16:45:06
*************************************************************/

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cctype>
using namespace std;
typedef long long ll;

inline ll read() {
    char ch = getchar(); ll x = 0; int op = 1;
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') op = -1;
    for(; isdigit(ch); ch = getchar()) x = x*10+ch-'0';
    return x*op;
}
inline void write(ll a) {
    if(a < 0) putchar('-'), a = -a;
    if(a >= 10) write(a/10); putchar('0'+a%10);
}

const int N = 100010, M = 200010;
int n, m, cnt, bsz, tot, clk, ret;//bsz是块的大小,tot是块的个数
int head[N], pos[N], a[N], b[N], num[N], f[N][25], bel[N], depth[N], sz[N], tong[N], ans[N];
struct edge {
    int to, nxt;
    edge() {}
    edge(int x, int y) { to = x, nxt = y; }
}e[N<<1];
struct node { int x, y, id; }q[M];

const bool cmp(const node &x, const node &y) { return bel[x.x] < bel[y.x] || bel[x.x] == bel[y.x] && pos[x.y] < pos[y.y]; }
inline void addedge(int x, int y) { e[++ cnt] = edge(y, head[x]); head[x] = cnt; }
inline void dfs(int u, int lst, int s) {
    pos[u] = ++ clk;
    if(sz[tot] == bsz) sz[bel[u] = ++ tot] = 1; else sz[tot] ++, bel[u] = tot;
    f[u][0] = lst; depth[u] = s;
    for(int i = head[u]; i; i = e[i].nxt) if(e[i].to != lst) dfs(e[i].to, u, s+1);
}
inline int lca(int x, int y) {
    if(depth[x] < depth[y]) swap(x, y);
    int tmp = depth[x]-depth[y];
    for(int i = 20; i >= 0; i --)
        if(tmp>>i&1) x = f[x][i];
    if(x == y) return x;
    for(int i = 20; i >= 0; i --)
        if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
    return f[x][0];
}
inline void work(int x, int flag) {
    if(flag) { tong[x] ++; if(tong[x] == 1) ret ++; }
    else { tong[x] --; if(tong[x] == 0) ret --; }
}
inline void change(int x, int y) {//把x~y路径上的点全部翻转(除了y 
    while(x != y) {
        b[x] ^= 1; work(a[x], b[x]);//b[i]存i号节点是否在路径上
        x = f[x][0];
    }
}
int main() {
    n = read(), m = read();
    for(int i = 1; i <= n; i ++) num[i] = a[i] = read();
    sort(num+1, num+1+n); tot = unique(num+1, num+1+n)-num-1;
    for(int i = 1; i <= n; i ++) a[i] = lower_bound(num+1, num+1+tot, a[i])-num;
    tot = 0;
    for(int i = 1; i < n; i ++) {
        int x = read(), y = read();
        addedge(x, y); addedge(y, x);
    } bsz = sqrt(n); bel[0] = ++ tot;
    dfs(1, 0, 0);
    for(int j = 1; j <= 20; j ++)
        for(int i = 1; i <= n; i ++) f[i][j] = f[f[i][j-1]][j-1];
    for(int i = 1; i <= m; i ++) {
        q[i].x = read(), q[i].y = read(); q[i].id = i;
    }
    sort(q+1, q+1+m, cmp);
    int l = 1, r = 1; ret = 0;//ret当前询问的答案,l,r是当前指针指向的两个点 
    for(int i = 1; i <= m; i ++) {
        int x = q[i].x, y = q[i].y, z;
        //l到x路径上除lca(l,x)的点全部翻转,r同理
        z = lca(l, x); change(l, z); change(x, z);
        z = lca(r, y); change(r, z); change(y, z);
        z = lca(x, y); b[z] ^= 1; work(a[z], b[z]);//翻转lca
        ans[q[i].id] = ret;//统计答案
        b[z] ^= 1; work(a[z], b[z]);//再翻转回来
        l = x, r = y;
    }
    for(int i = 1; i <= m; i ++) printf("%d\n", ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值