题意
给定一棵n个点的有根树,编号依次为1到n,其中1号点是根节点。每个节点都被染上了某一种颜色,其中第i个节点的颜色为c[i]。如果c[i]=c[j],那么我们认为点i和点j拥有相同的颜色。定义depth[i]为i节点与根节点的距离,为了方便起见,你可以认为树上相邻的两个点之间的距离为1。站在这棵色彩斑斓的树前面,你将面临m个问题。每个问题包含两个整数x和d,表示询问x子树里且depth不超过depth[x]+d的所有点中出现了多少种本质不同的颜色。请写一个程序,快速回答这些询问。
正整数(T(1<=T<=500)),表示测试数据的组数。
正整数(n(1<=n<=100000))和(m(1<=m<=100000)),表示节点数和询问数。
分析
参照LowestJN的题解。
先考虑没有深度限制,要计算子树中不同的颜色的个数,就令所有节点的初始权值为1,就把两两相同颜色的节点的LCA的权值-1,然后求一遍子树权值和就可以了。
但是这样做复杂度是(O(n^2)),而且会在某个节点多次-1。其实只要把颜色相同的节点提出来,按照dfs序的标号排个序,然后在相邻的节点的LCA处-1就行了。
然后考虑有深度限制,仔细想想发现这个限制特别像主席树,那么就以深度排序,以深度为时间点建主席树,那么上面说的对节点修改的操作也要在这个时候做,可以用set维护同一颜色以dfn为序的前驱后继。
时间复杂度(O(n log n + m log n))。
有趣的空间问题,理论(O(n log n)),实际上处理不同深度的时候同一节点最多会操作树链4次,(4 log_2 n = 66.438561897747246957406388589788),而我的程序要开到70n才能过。
#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read()
{
rg T data=0;
rg int w=1;
rg char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')
w=-1;
ch=getchar();
}
while(isdigit(ch))
{
data=data*10+ch-'0';
ch=getchar();
}
return data*w;
}
template<class T>il T read(rg T&x)
{
return x=read<T>();
}
typedef long long ll;
co int N=1e5+1;
int c[N];
int nx[N],to[N],fa[N],dep[N],siz[N],son[N];
void dfs1(int x)
{
dep[x]=dep[fa[x]]+1,siz[x]=1,son[x]=0;
for(int i=to[x];i;i=nx[i])
{
dfs1(i);
siz[x]+=siz[i];
if(siz[i]>siz[son[x]])
son[x]=i;
}
}
int dfn,pos[N],ref[N],top[N];
void dfs2(int x,int top)
{
pos[x]=++dfn,ref[dfn]=x,::top[x]=top;
if(!son[x])
return;
dfs2(son[x],top);
for(int i=to[x];i;i=nx[i])
{
if(i==son[x])
continue;
dfs2(i,i);
}
}
int lca(int x,int y)
{
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]])
std::swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
int id[N];
bool cmp(int u,int v)
{
return dep[u]<dep[v];
}
std::set<int>S[N];
std::set<int>::iterator it;
co int LG=70;
int root[N],tot;
namespace T
{
int L[N*LG],R[N*LG],sum[N*LG];
int clone(int y)
{
int x=++tot;
L[x]=L[y],R[x]=R[y],sum[x]=sum[y];
return x;
}
void insert(int&x,int l,int r,int p,int v)
{
x=clone(x);
sum[x]+=v;
if(l==r)
return;
int m=(l+r)/2;
if(p<=m)
insert(L[x],l,m,p,v);
else
insert(R[x],m+1,r,p,v);
}
int query(int x,int l,int r,int ql,int qr)
{
if(ql<=l&&r<=qr)
return sum[x];
int m=(l+r)/2;
if(qr<=m)
return query(L[x],l,m,ql,qr);
if(ql>=m+1)
return query(R[x],m+1,r,ql,qr);
return query(L[x],l,m,ql,qr)+query(R[x],m+1,r,ql,qr);
}
}
int main()
{
// freopen("BZOJ4771.in","r",stdin);
// freopen("BZOJ4771.out","w",stdout);
int t;
read(t);
while(t--)
{
int n,m;
read(n),read(m);
for(int i=1;i<=n;++i)
{
read(c[i]);
to[i]=nx[i]=0;
id[i]=i;
S[i].clear();
}
for(int i=2;i<=n;++i)
{
read(fa[i]);
nx[i]=to[fa[i]],to[fa[i]]=i;
}
dfs1(1);
dfn=0;
dfs2(1,1);
std::sort(id+1,id+n+1,cmp);
root[0]=0,tot=0;
int p=1;
for(int i=1;i<=n;++i)
{
root[i]=root[i-1];
while(p<=n&&dep[id[p]]<=i)
{
int x=0,y=0,pcol=c[id[p]];
it=S[pcol].lower_bound(pos[id[p]]);
if(it!=S[pcol].end())
y=ref[*it];
if(it!=S[pcol].begin())
x=ref[*--it];
T::insert(root[i],1,n,pos[id[p]],1);
if(x)
T::insert(root[i],1,n,pos[lca(x,id[p])],-1);
if(y)
T::insert(root[i],1,n,pos[lca(id[p],y)],-1);
if(x&&y)
T::insert(root[i],1,n,pos[lca(x,y)],1);
S[pcol].insert(pos[id[p]]);
++p;
}
}
int ans=0;
while(m--)
{
int x=read<int>()^ans,d=read<int>()^ans;
d=std::min(dep[x]+d,n);
printf("%d
",ans=T::query(root[d],1,n,pos[x],pos[x]+siz[x]-1));
}
}
return 0;
}
第一の問題
问题描述
给定一个序列,每个元素有两种属性,一个是颜色,一个是权值。
每次询问一个区间内权值小于某值的元素中不同颜色的个数。
规定询问的区间不相交(可能包含)。
输入格式
第一行一个正整数n,表示序列长度。
接下来n行,每行两个整数,表示颜色和权值。
下一行一个整数m,表示询问次数。
最后m行,每行两个整数l,r,v,表示询问[l,r]区间内权值小于v的元素中不同颜色的个数。
强制在线的方式:对于输入的l,r,v,异或上lastans。规定第一次询问操作的lastans=0。
输出格式
输出m行每行一个整数,分别对应答案。
样例输入
样例输出
数据范围
对于100%的数据,(1 leq n,m leq 10^5)。