题目链接:http://www.spoj.com/problems/COT2/en/
题目大意:
题意很简单,就是问你一棵树上任意两点间都多少不同的点。
解题思路:
这道题用到了传说中的树上莫队,其实还是很懵逼的,还不是很了解树上莫队的转换。树上莫队的详细解析可以看这篇博客http://blog.csdn.net/discreeter/article/details/52372689,自己讲是真的很难讲清楚,最后其实就是根据点的出现次数决定是否计算该点对答案的贡献。
自己代码也是照着大佬的来写的,主要根据代码来讲吧。
Ac代码:
#include<bits/stdc++.h>
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
typedef long long ll;
const int N=1e5+5;
const int INF=1e9+7;
int dx[4]={0,1,0,-1};
int dy[4]={1,0,-1,0};
int n,m,a[N];
int si,tag,pos[N]; //分块
int dep[N],par[N][21]; //dfs和求lca
int tmp,vis[N],tot[N],res[N]; //莫队算法
struct node //储存询问
{
int l,r;
int id;
}qu[N];
stack<int> s; //建立一个栈用于树分块
vector<int> v; //离散化
vector<int> vk[N];//建图
int getid(int x) {return lower_bound(v.begin(),v.end(),x)-v.begin()+1;}
bool cmp(const node &p,const node &q) //根据点所在块进行排序
{
if(pos[p.l]==pos[q.l])
return pos[p.r]<pos[q.r];
return pos[p.l]<pos[q.l];
}
void init()
{
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];
}
int dfs(int k) //树分块
{
int num=0;
for(int i=0;i<(int)vk[k].size();i++)
{
int u=vk[k][i];
if(!dep[u])
{
dep[u]=dep[k]+1,par[u][0]=k;
num+=dfs(u);
if(num>=si)
{
while(num--)
{
pos[s.top()]=tag;
s.pop();
}
tag++;
}
}
}
s.push(k);
return num+1;
}
int lca(int vv,int uu) //求lca
{
if(dep[vv]<dep[uu])
swap(uu,vv);
int d=dep[vv]-dep[uu];
for(int i=0;(d>>i)!=0;i++)
if((d>>i)&1) vv=par[vv][i];
if(vv==uu)
return vv;
for(int i=20;i>=0;i--)
{
if(par[vv][i]!=par[uu][i])
{
vv=par[vv][i];
uu=par[uu][i];
}
}
return par[vv][0];
}
void slove(int &v) //莫队转移
{
if(vis[v]) //如果该点之前出现过
{
if(--tot[a[v]]==0) //出现次数-1 并判断-1后是否为0
tmp--; //为0则去掉对答案的贡献
}
else if(++tot[a[v]]==1) //判断出现次数+1后是否为1
tmp++; //增加对答案的贡献
vis[v]^=1; //取反
v=par[v][0];
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),v.push_back(a[i]); //离散化 跟qsc大佬学的 感觉挺方便的
sort(v.begin(),v.end()),v.erase(unique(v.begin(),v.end()),v.end());
for(int i=1;i<=n;i++)
a[i]=getid(a[i]);
for(int i=1;i<n;i++) //建图
{
int uu,vv;
scanf("%d%d",&uu,&vv);
vk[uu].push_back(vv);
vk[vv].push_back(uu);
}
si=(int)sqrt(n); //分块及对树进行dfs
dep[1]=1;
dfs(1);
while(!s.empty()) //多余的点重新分一个块
{
pos[s.top()]=tag;
s.pop();
}
init();
for(int i=1;i<=m;i++)
{
scanf("%d%d",&qu[i].l,&qu[i].r);
if(pos[qu[i].l]>pos[qu[i].r]) //保持pos小的在前
swap(qu[i].l,qu[i].r);
qu[i].id=i;
}
sort(qu+1,qu+1+m,cmp); //对询问进行离线排序
tmp=0;
int cv=1,cu=1;
for(int i=1;i<=m;i++) //进行树上莫队的转移
{
int nv=qu[i].l,nu=qu[i].r;
int la=lca(cv,nv);
while(cv!=la) slove(cv);
while(nv!=la) slove(nv);
la=lca(cu,nu);
while(cu!=la) slove(cu);
while(nu!=la) slove(nu);
cv=qu[i].l,cu=qu[i].r;
la=lca(cv,cu);
res[qu[i].id]=tmp+(!tot[a[la]]);
}
for(int i=1;i<=m;i++)
printf("%d\n",res[i]);
}