HNOI2015 开店

题目大意:给出一棵边带权的树(每个点的度小于等于3),每个点都带有一个年龄值v,强制在线询问年龄值在[l,r]区间内的所有点到指定点u的距离和。

        这道题有两种做法,两种做法都比较有代表性。

        第一种是树链剖分加可持久化线段树,我们先考虑简化问题,假设我们忽略年龄值,而只是在线询问所有点到一个点的距离和呢?转换一下,设dep(u)代表u节点到根的距离和,那么答案就等于n*dep(u)+所有点到根的距离(这两者都可以预处理)-LCA,即我们将求每对点对的距离和的问题转化为求所有点与点u的LCA的dep之和,然而仅仅只是这样我们依然无法解决这个问题,这个时候,我们就需要换位思考,我们之前想的是正着求所有LCA的dep之和,现在我们反过来,我们求哪些边的权值会被计算到LCA的dep里面,我们发现,LCA只可能是u以及u的祖先,能够被计算为LCA的dep的边权也只会是连接这些点的边,那么每条边会被计算多少次呢?很明显,这条边有多少个儿子就被计算了多少次,而总共的LCA的dep之和就是所有的u的祖先边的权值乘上儿子个数的和,如果题目就到这里,那么我们只需要通过两遍DFS的预处理就可以直接O(1)回答我们的问题了,然而这道题却偏偏设置了一个年龄的区间限制,那么我们首先可以想到差分,然后我们将所有点按照年龄排序,一个点一个点地添加进去,每个点会对他的所有祖先边贡献等同于他的祖先边的权值,这满足叠加性,所以我们可以使用树剖来完成这个过程,然后把树剖用的线段树做成可持久化,就可以使用差分了。这种方法是非常快的,但是转换起来有些困难。

        第二种是动态树分治,这是一种比较直观且非常暴力的方法,首先我们考虑,假如这个问题是离线的,那么我们完全可以使用点分治直接搞定(加一个小小的容斥)。那么只有一个询问呢?虽然我们可以直接DFS,O(n)解决——这个不在考虑范围之内,但是我们也可以强行犯傻,使用点分治来求,由于每个点的度小于等于3,这给了我们以方便,我们对于每个重心,先找到我们要求的那个点所在的子树,然后再遍历其余的最多2个子树,对每个子树进行排序之后,算出前缀和然后差分加二分,而对于强制的在线询问来说,我们只需要提前将点分治的那个过程记录下来,往细了说就是,记录下一个点的所有父亲(此父亲非彼父亲,其意思那些从它出发能够找到这个点的所有重心),并记录下这个点属于它的父亲的哪个子树,并记下这个点到它的父亲的距离,然后记录下在点分治过程中所有重心的子树(按照年龄排序并前缀和处理好),做了这些处理之后,我们就可以来在线询问了,对于某个制定的点,我们取出它的所有父亲,由于我们知道它在它的父亲中的哪个子树中,所以我们可以调出另外两个子树,二分处理取前缀和做差分,然后由于最多只有log层,所以总的时间复杂度为n(log2n)^2,这个复杂度看起来和前面的那个一样,但是太暴力了,常数很大,不做点常数优化是过不了OJ的,而且很难写……当然,第一种方法也不算好写。

        

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
int n,q,A,cnt=0,root=0,sum;
int first[150005]={0};
const int INF=0x3f3f3f3f;
long long ans=0;
struct Edge
{
	int next,to,v;
}edge[300005];
struct Son
{
	long long dis;
	int age;
	void Insert(long long x,int y){dis=x;age=y;}
};
struct Fa
{
	int dis,top,num;
	void Insert(int x,int y,int z){dis=x;top=y;num=z;}
};
struct Tree
{
	int age,son,f;
	bool vis;
	vector<Son>sons[3];
	vector<Fa>fa;
}tree[150005];
inline void add(int x,int y,int v)
{
	cnt++;
	edge[cnt].to=y;
	edge[cnt].v=v;
	edge[cnt].next=first[x];
	first[x]=cnt;
}
void Get(int u,int fa)
{
	tree[u].son=1;
	tree[u].f=0;
	for(int i=first[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==fa||tree[v].vis)continue;
		Get(v,u);
		tree[u].son+=tree[v].son;
		tree[u].f=max(tree[u].f,tree[v].son);
	}
	tree[u].f=max(tree[u].f,sum-tree[u].son);
	if(tree[u].f<tree[root].f)root=u;
}
void DFS(int u,int fa,int top,int dis,int num)
{
	Fa temp1;
	temp1.Insert(dis,top,num);
	tree[u].fa.push_back(temp1);
	Son temp2;
	temp2.Insert(dis,tree[u].age);
	tree[top].sons[num].push_back(temp2);
	for(int i=first[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(tree[v].vis||v==fa)continue;
		DFS(v,u,top,dis+edge[i].v,num);
	}
}
void Solve(int x)
{
	int now=0;
	Fa temp;
	temp.Insert(0,x,3);
	tree[x].fa.push_back(temp);
	tree[x].vis=1;
	for(int i=first[x];i;i=edge[i].next)
	{
		int y=edge[i].to;
		if(tree[y].vis)continue;
		DFS(y,x,x,edge[i].v,now++);
	}
	for(int i=first[x];i;i=edge[i].next)
	{
		int y=edge[i].to;
		if(tree[y].vis)continue;
		sum=tree[y].son;
		root=0;
		Get(y,x);
		Solve(root);
	}
}
inline bool cmp(Son a,Son b)
{
	return a.age<b.age;
}
int main(){
	scanf("%d%d%d",&n,&q,&A);
	for(int i=1;i<=n;i++)scanf("%d",&tree[i].age);
	int x,y,v,u,len,num,L,R,mid;
	long long suml;
	for(int i=1;i<n;i++)
	{
		scanf("%d%d%d",&x,&y,&v);
		add(x,y,v);
		add(y,x,v);
	}
	sum=n;
	tree[root].f=INF;
	Get(1,0);
	Solve(root);
	for(int i=1;i<=n;i++)
	   for(int j=0;j<3;j++)
	   {
	   	  sort(tree[i].sons[j].begin(),tree[i].sons[j].end(),cmp);
	   	  for(int l=1;l<tree[i].sons[j].size();l++)tree[i].sons[j][l].dis+=tree[i].sons[j][l-1].dis;
	   }
	ans=0;
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d%d",&v,&x,&y);
		x=(x+ans)%A; y=(y+ans)%A;
		ans=0;
		if(x>y)swap(x,y);
		for(int j=0;j<tree[v].fa.size();j++)
		{
			u=tree[v].fa[j].top;
			len=tree[v].fa[j].dis;
			num=tree[v].fa[j].num;
			if(x<=tree[u].age&&tree[u].age<=y)ans+=len;
			for(int l=0;l<3;l++)
			{
				if(l==num)continue;
				L=0,R=tree[u].sons[l].size()-1;
				if(L>R)continue;
				while(L<R)
				{
					mid=(L+R)/2;
					if(tree[u].sons[l][mid].age<=x-1)L=mid+1;
					else R=mid;
				}
				if(L==0)
				{
				   if(tree[u].sons[l][L].age>x-1)suml=0,L=-1;
				   else suml=tree[u].sons[l][L].dis,L=0;
			        }
				else
				{
					if(tree[u].sons[l][L].age<=x-1)L++;
					L--;
					suml=tree[u].sons[l][L].dis;
				}
				ans-=1ll*len*(L+1)+suml;
				L=0,R=tree[u].sons[l].size()-1;
				while(L<R)
				{
					mid=(L+R)/2;
					if(tree[u].sons[l][mid].age<=y)L=mid+1;
					else R=mid;
				}
				if(L==0)
				{
				   if(tree[u].sons[l][L].age>y)suml=0,L=-1;
				   else suml=tree[u].sons[l][L].dis,L=0;
			    }
				else
				{
					if(tree[u].sons[l][L].age<=y)L++;
					L--;
					suml=tree[u].sons[l][L].dis;
				}
				ans+=1ll*len*(L+1)+suml;
			}
		}
		cout<<ans<<'\n';
	}
	return 0;
}            

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值