BZOJ4771 七彩树

题意

给定一棵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\n",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\)

posted on 2019-01-25 11:42  autoint  阅读(150)  评论(0编辑  收藏  举报

导航