题目大意
一棵有
n
个节点的树,每个节点有颜色
有
m
个询问,每次询问点
1≤n≤50000,1≤m≤100000,1≤coli≤n
题目分析
这样的题目乍一看无从下手,颜色种数的查询使得线段树伸展树树套树等等都失去作用。
这个时候,我们就要考虑离线算法和
n√
算法。
我们可以使用莫队算法。考虑求出该树的括号序列(每个点在第一次进入和最后一次离开时都会放进序列,位置分别记为
apx
和
exx
)。
我们将路径查询转化为在括号序列中的区间查询,然后使用莫队算法,开一个布尔数组记录每个点是否出现,然后以此为删除还是增加的参照。
设路径两个端点分别为
u
和
第一种,两个点互不为祖先。这样的话,它们在括号序列中呈
u...u...v...v
的排列。显然求解区间为
[exu,apv]
。注意这个时候,
lca
没有被包括在内,所以我们在求答案时要考虑
lca
,但是如果用
lca
更新了当前答案值和颜色出现数组,会对以后的答案造成影响,所以我们不能直接更新当前答案值和颜色数组,应该打小特判修改答案本身(不清楚看代码实现)。
第二种,其中一个点是另一个点的祖先。这样的话,它们在括号序列中呈
u...v...v...u
那么求解区间就是
[apu,apv]
,没有什么细节。
特殊地,如果
u=v
,分为第一种情况,否则会出错(想想为什么)。
然后莫队的过程就不多说了,不会的去看看别人的博客或论文。
时间复杂度
O(nlogn2+nn√)
(
n,m
同阶,只讨论
n
)。
代码实现
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cctype>
#include <cmath>
using namespace std;
int read()
{
int x=0,f=1;
char ch=getchar();
while (!isdigit(ch))
{
if (ch=='-')
f=-1;
ch=getchar();
}
while (isdigit(ch))
{
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
const int N=50050;
const int M=100050;
const int C=50050;
const int B=N<<1;
const int E=N<<1;
const int EL=N<<1;
const int LGEL=17;
struct Q
{
int l,r,id,a,b,lca;
bool type;
}q[M];
int b;
bool operator<(Q x,Q y)
{
return ((x.l-1)/b+1<(y.l-1)/b+1)||((x.l-1)/b+1==(y.l-1)/b+1)&&(x.r<y.r);
}
int ap[N],ex[N],co[N],last[N],pos[N],deep[N],fa[N];
int n,m,idx,el,lgel,tot;
int next[E],tov[E];
int rmq[EL][LGEL];
int euler[EL];
bool exist[N];
int color[C];
int ans[M];
int bk[B];
void insert(int x,int y)
{
tov[++tot]=y;
next[tot]=last[x];
last[x]=tot;
}
void dfs(int x)
{
euler[++el]=x;
pos[x]=el;
bk[++idx]=x;
ap[x]=idx;
int i=last[x],y;
while (i)
{
y=tov[i];
if (fa[x]!=y)
{
fa[y]=x;
deep[y]=deep[x]+1;
dfs(y);
euler[++el]=x;
}
i=next[i];
}
bk[++idx]=x;
ex[x]=idx;
}
void pre()
{
for (int i=1;i<=el;i++)
rmq[i][0]=euler[i];
lgel=(int)trunc(log(el)/log(2));
for (int j=1;j<=lgel;j++)
for (int i=1;i<=el-(1<<j)+1;i++)
if (deep[rmq[i][j-1]]<deep[rmq[i+(1<<j-1)][j-1]])
rmq[i][j]=rmq[i][j-1];
else
rmq[i][j]=rmq[i+(1<<j-1)][j-1];
}
int RMQ(int l,int r)
{
int lgr=(int)trunc(log(r-l+1)/log(2));
if (deep[rmq[l][lgr]]<deep[rmq[r-(1<<lgr)+1][lgr]])
return rmq[l][lgr];
else
return rmq[r-(1<<lgr)+1][lgr];
}
int LCA(int x,int y)
{
x=pos[x],y=pos[y];
if (x>y)
x^=y^=x^=y;
return RMQ(x,y);
}
int now;
void change(int x)
{
exist[bk[x]]^=1;
if (exist[bk[x]])
{
if (!color[co[bk[x]]])
now++;
color[co[bk[x]]]++;
}
else
{
color[co[bk[x]]]--;
if (!color[co[bk[x]]])
now--;
}
}
void solve()
{
now=0;
for (int i=q[1].l;i<=q[1].r;i++)
change(i);
ans[q[1].id]=now;
if (q[1].type)
if (!color[co[q[1].lca]])
ans[q[1].id]++;
if (q[1].a!=q[1].b&&color[q[1].a]&&color[q[1].b])
ans[q[1].id]--;
int l=q[1].l,r=q[1].r;
for (int i=2;i<=m;i++)
{
while (l<q[i].l)
{
change(l);
l++;
}
while (q[i].l<l)
{
l--;
change(l);
}
while (r<q[i].r)
{
r++;
change(r);
}
while (q[i].r<r)
{
change(r);
r--;
}
ans[q[i].id]=now;
if (q[i].type)
if (!color[co[q[i].lca]])
ans[q[i].id]++;
if (q[i].a!=q[i].b&&color[q[i].a]&&color[q[i].b])
ans[q[i].id]--;
}
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
n=read(),m=read();
for (int i=1;i<=n;i++)
co[i]=read();
for (int i=1,x,y;i<=n;i++)
{
x=read(),y=read();
if (x*1ll*y)
insert(x,y),insert(y,x);
}
for (int i=1;i<=m;i++)
q[i].l=read(),q[i].r=read(),q[i].a=read(),q[i].b=read(),q[i].id=i;
deep[1]=1;
dfs(1);
pre();
for (int i=1;i<=m;i++)
{
q[i].lca=LCA(q[i].l,q[i].r);
if (ap[q[i].l]>ap[q[i].r])
swap(q[i].l,q[i].r);
if (ex[q[i].r]<ex[q[i].l])
{
q[i].type=0;
q[i].l=ap[q[i].l];
q[i].r=ap[q[i].r];
}
else
{
q[i].type=1;
q[i].l=ex[q[i].l];
q[i].r=ap[q[i].r];
}
}
b=trunc(sqrt(idx))+1;
sort(q+1,q+1+m);
solve();
for (int i=1;i<=m;i++)
printf("%d\n",ans[i]);
fclose(stdin);
fclose(stdout);
return 0;
}
参考资料
∙
《莫队算法详解》百度文库
∙
hzwer的莫队算法题库