# SPOJ COT2 Count on a tree II 树上莫队

S(v, u) = S(root, v) xor S(root, u) xor lca(v, u)

lca很讨厌，于是再定义
T(v, u) = S(root, v) xor S(root, u)

T(curV, curU) = S(root, curV) xor S(root, curU)
T(targetV, curU) = S(root, targetV) xor S(root, curU)

T(curV, curU) xor T(targetV, curU)= (S(root, curV) xor S(root, curU)) xor (S(root, targetV) xor S(root, curU))

T(curV, curU) xor T(targetV, curU)= S(root, curV) xorS(root, targetV)

T(targetV, curU)= T(curV, curU) xor S(root, curV) xor S(root, targetV)

T(targetV, curU)= T(curV, curU) xor T(curV, targetV)
（有公式恐惧症的不要走啊 T_T）

——vfk博客

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define debug() puts("here")
using namespace std;

const int N = 500010;
int n, m, unit;
int arr[N], brr[N];
struct edge
{
int to, next;
} g[N*2];
struct node
{
int l, r, v, u, id;
friend bool operator< (node a, node b)
{
return a.l != b.l ? a.l < b.l : a.r < b.r;
}
}q[N*2];
int dep[N], par[N][21];
int pos[N], st[N], res[N*2];
int tmp, top, tag;
int tot[N], vis[N];
{
}
int dfs(int v)
{
int num = 0;
for(int i = head[v]; i != -1; i = g[i].next)
{
int u = g[i].to;
if(! dep[u])
{
dep[u] = dep[v] + 1, par[u][0] = v;
num += dfs(u);
if(num >= unit) //分块
{
while(num--) pos[st[--top]] = tag;
tag++;
}
}
}
st[top++] = v; //储存待分块的序列
return num + 1; //向上一层返回已被访问还未分块的点的个数
}
int LCA(int v, int u) //倍增法求LCA
{
if(dep[v] < dep[u]) swap(v, u);
int d = dep[v] - dep[u];
for(int i = 0; (d>>i) != 0; i++)
if((d>>i) & 1) v = par[v][i];
if(v == u) return v;
for(int i = 20; i >= 0; i--)
if(par[v][i] != par[u][i]) v = par[v][i], u = par[u][i];
return par[v][0];
}
void work(int &v)
{
if(vis[v]) //已被记录，则本次去掉此点
{
if(--tot[arr[v]] == 0) tmp--;
}
else if(++tot[arr[v]] == 1) tmp++;
vis[v] ^= 1;
v = par[v][0];
}
void solve()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", &arr[i]), brr[i] = arr[i];
sort(brr + 1, brr + 1 + n);
for(int i = 1; i <= n; i++) arr[i] = lower_bound(brr + 1, brr + 1 + n, arr[i]) - brr;
int a, b;
cnt = 0;
for(int i = 1; i < n; i++)
unit = (int)sqrt(n);
dep[1] = 1;
dfs(1);
while(top) pos[st[--top]] = tag; //最后一部分没有分块的点,分块
for(int j = 1; (1<<j) <= n; j++)
for(int i = 1; i <= n; i++)
par[i][j] = par[par[i][j-1]][j-1];
for(int i = 1; i <= m; i++)
{
scanf("%d%d", &q[i].v, &q[i].u);
if(pos[q[i].v] > pos[q[i].u]) swap(q[i].v, q[i].u);
q[i].id = i, q[i].l = pos[q[i].v], q[i].r = pos[q[i].u]; //确定两个点分别位于的块
}
sort(q + 1, q + 1 + m); //分块排序
tmp = 0;
int cv = 1, cu = 1;
for(int i = 1; i <= m; i++)
{
int nv = q[i].v, nu = q[i].u;
int lca = LCA(cv, nv);//两点朝lca移动，处理路径上的点
while(cv != lca) work(cv);
while(nv != lca) work(nv);
lca = LCA(cu, nu);
while(cu != lca) work(cu);
while(nu != lca) work(nu);
cv = q[i].v, cu = q[i].u;
lca = LCA(cv, cu);
res[q[i].id] = tmp + (!tot[arr[lca]]);//对lca特殊处理
}
for(int i = 1; i <= m; i++) printf("%d\n", res[i]);
}
int main()
{
solve();
return 0;
}


#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;

typedef long long ll;
const int N = 50010;
struct edge
{
int to, next;
}g[N*2];
struct node
{
int l, r, anc, id;
}p[N*2];
int n, m, unit;
int arr[N], brr[N];
int par[N][20], dep[N];
int in[N], out[N], lst[N*2], num;
int res[N*2], tmp, ver[N];
bool vis[N];
{
}
void dfs(int v)
{
in[v] = ++num;
lst[num] = v;
for(int i = head[v]; i != -1; i = g[i].next)
{
int u = g[i].to;
if(! dep[u])
par[u][0] = v, dep[u] = dep[v] + 1, dfs(u);
}
out[v] = ++num;
lst[num] = v;
}
int LCA(int v, int u)
{
if(dep[v] < dep[u]) swap(v, u);
int d = dep[v] - dep[u];
for(int i = 0; (d>>i) != 0; i++)
if((d>>i) & 1) v = par[v][i];
if(v == u) return v;
for(int i = 19; i >= 0; i--)
if(par[v][i] != par[u][i]) v = par[v][i], u = par[u][i];
return par[v][0];
}
void update(int i)
{
//树上每个点在每次查询时只能被访问一次，若之前没访问过，那就是说明当前点应当在路径中，否则，不在路径中
if(vis[lst[i]]) //当前点被访问过，那么一定被计数了
{
ver[arr[lst[i]]]--;
if(! ver[arr[lst[i]]]) tmp--; //当这个权值不存在时，才把计数减1
vis[lst[i]] = false;
}
else
{
if(! ver[arr[lst[i]]]) tmp++; //当这个权值本来不存在时，把计数加1
ver[arr[lst[i]]]++;
vis[lst[i]] = true;
}
}
void solve()
{
int a, b;
for(int i = 1; i <= n; i++)
scanf("%d", &arr[i]), brr[i] = arr[i];
sort(brr + 1, brr + 1 + n);
for(int i = 1; i <= n; i++) arr[i] = lower_bound(brr + 1, brr + 1 + n, arr[i]) - brr; //离散化
cnt = 0;
for(int i = 1; i <= n - 1; i++) //存树
memset(dep, 0, sizeof dep);
dep[1] = 1;
num = 0;
par[1][0] = 1;
dfs(1);
for(int j = 1; (1<<j) <= n; j++) //倍增法预处理
for(int i = 1; i <= n; i++)
par[i][j] = par[par[i][j-1]][j-1];
for(int i = 1; i <= m; i++)
{
scanf("%d%d", &a, &b);
p[i].id = i;
if(a == b) p[i].anc = -1; //特殊标记，此时公共祖先应该为1
else
{
p[i].anc = LCA(a, b);
if(p[i].anc != a && p[i].anc != b) //画个图很快就可以看明白
p[i].l = min(out[a], out[b]), p[i].r = max(in[a], in[b]);
else
p[i].l = min(in[a], in[b]), p[i].r = max(in[a], in[b]);
}
}
unit = (int)sqrt(num);
memset(vis, 0, sizeof vis);
sort(p + 1, p + 1 + m, [](node a, node b){return a.l/unit != b.l/unit ? a.l/unit < b.l/unit : a.r < b.r;});//分块排序
int l = 1, r = 0;
tmp = 0;
for(int i = 1; i <= m; i++)
if(p[i].anc == -1) res[p[i].id] = 1; //查询的两点为同一点，则种类只有1
else
{
for(; r < p[i].r; r++) update(r + 1);
for(; r > p[i].r; r--) update(r);
for(; l < p[i].l; l++) update(l);
for(; l > p[i].l; l--) update(l - 1);
if(lst[p[i].l] == p[i].anc || lst[p[i].r] == p[i].anc) res[p[i].id] = tmp; //其中一点为两点的公共祖先
else
{ //此时所访问的路径中不包括两点的lca，所以要对lca特别处理
update(in[p[i].anc]);
res[p[i].id] = tmp;
update(in[p[i].anc]);
}
}
for(int i = 1; i <= m; i++) printf("%d\n", res[i]);
}
int main()
{
scanf("%d%d", &n, &m);
solve();
return 0;
}


#### SPOJ COT2 树上的莫队算法，树上区间查询

2015-08-17 19:09:00

#### SPOJ COT2

2015-04-08 17:26:52

#### spojCOT2 Count on a tree II

2018-01-20 17:36:54

#### SPOJ COT2 Count on a tree II (树上莫队)

2018-05-26 11:58:20

#### 【SPOJ COT2】Count on a tree II（树上莫队）

2018-05-14 09:51:47

#### SPOJ COT2(树上莫队)

2017-11-06 16:22:27

#### spoj_cot2 Count on a tree II(树上莫队+离散化)

2018-03-18 21:24:54

#### SPOJ COT2 Count on a tree II [树上莫队]

2015-09-10 11:52:42

#### [spoj COT2- Count on a tree II] 树上莫队

2017-08-30 15:42:12

#### 2588: Spoj 10628. Count on a tree

2015-12-15 18:16:05