码迷,mamicode.com
首页 > 其他好文 > 详细

luogu U95602 射手座之日

时间:2020-06-25 11:55:30      阅读:49      评论:0      收藏:0      [点我收藏+]

标签:long   namespace   +=   lca   技巧   const   使用   alc   i++   

先考虑一个简单容斥:设 \(val(x)\) 表示 \(x\) 子树内所有点作为 \(LCA\) 的贡献之和,那么以 \(x\)\(LCA\) 的贡献就是 \(val(x)-\sum\limits_{v\in son\ of\ x}val(v)\)

现在的问题就是如何求 \(val(x)\) 。我们发现,在把 \(x\) 子树内每个点在关卡表都标注出来后,总贡献就是 \(\sum\limits_{len}\frac{len\times (len+1)}{2}\),其中 \(len\) 表示现在关卡表中一个极长子段的长度。

遇到对每个子树都要处理的题目,我们很自然的想到了 \(dsu\ on\ tree\),当然这里还有一个很妙的小技巧:对于每个节点 \(x\),我们在标记它的子树时使用 \(dfn_x\),然后在重儿子处理过后直接赋值 \(dfn_x=dfn_{height\_son[x]}\),这样就能省去很多很多麻烦的撤销操作(我一开始还准备写可撤销并查集来着)。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

typedef long long LL;
const int N=200009;
int n,head[N],cnt,p[N],a[N],son[N],siz[N],L[N],R[N],l[N],r[N],Index,DFN[N],rev[N],vis[N],now;
LL ans[N],Ans,s[N];
struct Edge
{
	int nxt,to;
}g[N*2];

void add(int from,int to)
{
	g[++cnt].nxt=head[from];
	g[cnt].to=to;
	head[from]=cnt;
}

void init()
{
	scanf("%d",&n);
	for (int i=2,x;i<=n;i++)
	{
		scanf("%d",&x);
		add(x,i),add(i,x);
	}
	for (int i=1,x;i<=n;i++)
		scanf("%d",&x),a[x]=i;
	for (int i=1;i<=n;i++)
		scanf("%d",&p[i]);
}

void dfs(int x,int fa)
{
	siz[x]=1,DFN[x]=++Index,rev[Index]=x;
	for (int i=head[x];i;i=g[i].nxt)
	{
		int v=g[i].to;
		if(v==fa)
			continue;
		dfs(v,x);
		siz[x]+=siz[v];
		if(siz[son[x]]<siz[v])
			son[x]=v;	
	}
	L[x]=DFN[x],R[x]=Index;
}

LL calc(int x) { return 1ll*x*(x+1)/2; }

void Insert(int x,int k)
{
	vis[x]=now,l[x]=r[x]=x;
	if(vis[x+1]!=now) l[x+1]=r[x+1]=0;
	if(vis[x-1]!=now) l[x-1]=r[x-1]=0;
	int xl=0,xr=0;
	if(l[x-1]&&r[x+1])
	{
		xl=x-l[x-1],xr=r[x+1]-x;
		r[l[x-1]]=r[x+1],l[r[x+1]]=l[x-1];
	}
	else if(l[x-1])
	{
		xl=x-l[x-1];
		r[l[x-1]]=x,l[x]=l[x-1];
	}
	else if(r[x+1])
	{
		xr=r[x+1]-x;
		l[r[x+1]]=x,r[x]=r[x+1];
	}
	ans[k]=ans[k]-calc(xl)-calc(xr)+calc(xl+xr+1);
}

void DFS(int x,int fa)
{
	for (int i=head[x];i;i=g[i].nxt)
	{
		int v=g[i].to;
		if(v==fa||v==son[x])
			continue;
		DFS(v,x);
	}
	now=DFN[x];
	if(son[x])
	{
		DFS(son[x],x);
		ans[x]+=ans[son[x]];
		DFN[x]=now=DFN[son[x]];
		for (int i=head[x];i;i=g[i].nxt)
		{
			int v=g[i].to;
			if(v==fa||v==son[x])
				continue;
			for (int j=L[v];j<=R[v];j++)
				Insert(a[rev[j]],x);
		}
	}
	Insert(a[x],x);
	LL k=ans[x];
	for (int i=head[x];i;i=g[i].nxt)
	{
		int v=g[i].to;
		if(v==fa) continue;
		k-=ans[v];
	}
	Ans+=k*p[x];
}

void work()
{
	dfs(1,-1);
	DFS(1,-1);
	printf("%lld\n",Ans);
}

int main()
{
	init();
	work();
	return 0;
}

luogu U95602 射手座之日

标签:long   namespace   +=   lca   技巧   const   使用   alc   i++   

原文地址:https://www.cnblogs.com/With-penguin/p/13191253.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!