题意:给出一棵树,共有N个节点,每个节点拥有一个编号和权值,询问M次,每次询问给出两个节点的编号,问这条路径上的所有节点有几个不同的权值。
范围:N<=4W M<=10W 权值<=10的9次
解法:树上莫队的经典题,一开始自己推,分块部分出错了,导致一直TLE....
实际上应该在搜索时定义一个SIZE,每次搜索子节点,SZIE加上子节点分块后剩下的节点,如果SIZE大于了根号N,就分块,否则把自己这个节点入栈,返回SIZE
询问排序时,要根据x,y的块来排序,这样可以让我每次拨动节点的时候,保证最多移动根号N个。
转移部分,可以考虑将X拨动到NX位置,那么NX向上走到LCA,X走到LCA,将路径取反即可(即存在性取反,这样走过两边的路就消掉了,因为X,Y两个节点走过相同的部分是不需要走的,也就是根节点到LCA(X,Y)的部分是不需要走的);
由于我是使用VIS【i】,表示i节点向上的边的存在性,所以vis[ LCA(x,y) ] 的存在性可能为0,但它又是必须取的,所以特判一下即可。
其他细节的实现,可以看代码:
AC代码(3秒左右):
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<iostream>
#include<stdlib.h>
#include<set>
#include<map>
#include<queue>
#include<vector>
#include<bitset>
#pragma comment(linker, "/STACK:1024000000,1024000000")
template <class T>
bool scanff(T &ret){ //Faster Input
char c; int sgn; T bit=0.1;
if(c=getchar(),c==EOF) return 0;
while(c!='-'&&c!='.'&&(c<'0'||c>'9')) c=getchar();
sgn=(c=='-')?-1:1;
ret=(c=='-')?0:(c-'0');
while(c=getchar(),c>='0'&&c<='9') ret=ret*10+(c-'0');
if(c==' '||c=='\n'){ ret*=sgn; return 1; }
while(c=getchar(),c>='0'&&c<='9') ret+=(c-'0')*bit,bit/=10;
ret*=sgn;
return 1;
}
#define inf 1073741823
#define llinf 4611686018427387903LL
#define PI acos(-1.0)
#define lth (th<<1)
#define rth (th<<1|1)
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define drep(i,a,b) for(int i=a;i>=b;i--)
#define gson(i,root) for(int i=ptx[root];~i;i=ed[i].next)
#define tdata int testnum;scanff(testnum);for(int cas=1;cas<=testnum;cas++)
#define mem(x,val) memset(x,val,sizeof(x))
#define mkp(a,b) make_pair(a,b)
#define findx(x) lower_bound(b+1,b+1+bn,x)-b
#define pb(x) push_back(x)
using namespace std;
typedef long long ll;
const int NN=40100;
int n,m;
int a[NN],b[NN],bn; //b[]用来离散化a[]
int ans[100100],sum,cot[NN];
bool vis[NN];
//vis[]表示节点向上连边的存在性,cot[i]记录i的出现次数;
int f[NN][22],dep[NN]; //lca相关数组
int ptx[NN],lnum=0; //图相关数组
int top,stk[NN],block[NN],bcnt,bsize; //分块相关数组
struct edge{
int v,next;
}ed[NN*2];
inline void addline (int x,int y){
ed[lnum].v=y;
ed[lnum].next=ptx[x];
ptx[x]=lnum++;
}
struct query{
int x,y,idx;
}q[100100];
bool cmp (query x,query y){ //树上节点按块排序,保证相邻节点移动距离至多根号N
if(block[x.x]==block[y.x])return block[x.y]<block[y.y];
return block[x.x]<block[y.x];
}
// lca \ block
inline int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
drep(i,16,0)if(dep[f[x][i]]>=dep[y])x=f[x][i];
if(x==y)return x;
drep(i,16,0)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
return f[x][0];
}
inline void add_block(int cnt) {
bcnt++;
while(cnt--) block[stk[top--]] = bcnt;
}
int dfs(int x,int fa)
{
int sz=0;
dep[x]=dep[fa]+1;//dep数组初始化
gson(i,x){
int y=ed[i].v;
if(y==fa)continue;
f[y][0]=x;
sz+=dfs(y,x);
if(sz>=bsize)add_block(sz),sz=0;
//如果范围达到了上限,划分block
}
stk[++top]=x;
return sz+1;
}
void init_lca_block(){
bsize = max(1, (int)sqrt(n));
add_block(dfs(1, 0));
rep(k,1,16)
rep(i,1,n)f[i][k]=f[f[i][k-1]][k-1];
//使用前提,dep[0]为0
}
//solve
inline void change(int x){ //此节点向上的连边存在性改变
vis[x]^=1;
if(vis[x]&&++cot[a[x]]==1)sum++;
if(!vis[x]&&--cot[a[x]]==0)sum--;
}
inline void update(int x,int y){ //x->y路径存在性改变
if(dep[x]<dep[y])swap(x,y);
while(dep[x]!=dep[y])change(x),x=f[x][0];
while(y!=x){
change(x);x=f[x][0];
change(y);y=f[y][0];
}
}
void solve(){
int x=1,y=1;//x,y指向根节点
rep(i,1,m){
update(x,q[i].x);x=q[i].x;//把x移到询问区间的x
update(y,q[i].y);y=q[i].y;//把y移到询问区间的y
int lcaxy=lca(x,y);
if(cot[a[lcaxy]]==0)ans[q[i].idx]=sum+1;//如果根节点没有被计数
else ans[q[i].idx]=sum;
}
rep(i,1,m)printf("%d\n",ans[i]);
}
void init(){
bn=sum=lnum=0;
mem(cot,0);
mem(vis,0);
mem(ptx,-1);
}
int main(){
init();
scanf("%d%d",&n,&m);
//读入并离散化a[]
rep(i,1,n)scanf("%d",&a[i]),b[++bn]=a[i];
sort(b+1,b+1+bn);
bn=unique(b+1,b+1+bn)-b-1;
rep(i,1,n)a[i]=findx(a[i]);
//读入边,并初始化lca和block
int x,y;
rep(i,1,n-1){
scanf("%d%d",&x,&y);
addline(x,y);
addline(y,x);
}
init_lca_block();
//读入询问,排序后用莫队算法求解
rep(i,1,m){
scanf("%d%d",&q[i].x,&q[i].y);
q[i].idx=i;
if(block[q[i].x]>block[q[i].y])swap(q[i].x,q[i].y);
}
sort(q+1,q+1+m,cmp);
solve();
return 0;
}