P5327 [ZJOI2019]语言

本文解析了一道ZJOI2019的算法竞赛题目,通过使用线段树和LCA算法解决树上的链不同点对数问题,详细介绍了斯坦纳树的概念及其在解题中的应用。

 

 P5327 [ZJOI2019]语言

题目描述

详见:P5327 [ZJOI2019]语言

简要题意:给定一棵树和一些链,问树上处于同一条链的不同点对数。

Solution

对于每一个点i,考虑以它为端点的可行路径有哪些。

我们可以发现,i可以到达的节点会组成一个斯坦纳树,这棵斯坦纳树包含了i即经过i链。

我们进一步可知,这棵斯坦纳树就是以i和经过i的所有链的端点u,v为关键点的最小斯坦纳树。

所以我们考虑对于每一个点,维护经过它的链组成的斯坦纳树是什么,顺便维护答案(斯坦纳树的边数)。

 

事实上,斯坦纳树的边数就是其中所有点的深度-dfs序相邻的点的LCA的深度,如下:

设斯坦纳树的点集为V,记i的dfs序为  dfn[i],深度为  dep[i]  。

若满足  \forall_{i<j}\;\;dfn[V[i]]<dfn[V[j]]  ,即dfs序按升序排序,则

ans_u=\sum_{i}{dep[V[i]]}-\sum_{i}{dep[lca(V[i],V[i+1])]}-dep[lca(V[k],V[1])]

Ans=\sum ans_i

 

可以用线段树合并(启发式合并也可)支持上述信息的维护。

线段树的下标为  dfn,并记录  [l,r]  中dfn最大的点ladfn 最小的点fi,以及 ans  。

合并时   ans_{l,r}=ans_{l,mid}+ans_{mid+1,r}-dep[lca(la_{l,mid},fi_{mid+1,r})]

统计答案时  Ans+=ans_{l,r}-dep[lca(fi_{l,r},la_{l,r})] 

 

因此,对于每一条路径  (u,v)  ,我们可以用树上差分把它拆成  (1,u),(1,v),(1,lca),(1,fa[lca])  这四条代价分别为+1,+1,-1,-1的链,分别放入权值线段树,并不断向上合并到根,沿路统计答案即可。

st表实现LCA,时间复杂度  O(nlgn)

如果写倍增LCA的会多一个log,卡卡常应该也能过。

 

PS:这道题写了1h,调了5h,原因是线段树动态开点要开log倍的内存。但我一直以为是dfs爆栈了(因为今早有一题先例),由于要用到dfs序,所以甚至在try coding 手工栈,However,WA声依旧,改得代码奇丑无比,自闭了一个下午,最后柳暗花明又一村,有一种想右转直行的冲动。

Code

最后贴一个丑陋的代码(早把罪恶的手工栈删了)。

#include<bits/stdc++.h>
#define RG register
typedef long long ll;
const int MAXN=4e5+50;
std::vector<int> e[MAXN],Del[MAXN];
int DFN=0,t=0,n,m;
int dfn[MAXN],st[MAXN][21],fa[MAXN];
int pre[MAXN],rt[MAXN],Log[MAXN];
ll dep[MAXN],ans=0;
inline int read()
{
    RG int x=0,f=1; char c=getchar();
    while (c<'0'||c>'9') { if(c=='-') f=-1; c=getchar(); }
    while (c>='0'&&c<='9') { x=(x<<3)+(x<<1)+(c^48); c=getchar(); }
    return x*f;
}
inline void get_dfn(int x,int father)
{
	dfn[x]=++DFN;
	pre[x]=++t; st[t][0]=x;
	fa[x]=father; dep[x]=dep[father]+1;
	for (RG int i=0;i<e[x].size();i++)
	    if (e[x][i]!=fa[x]) get_dfn(e[x][i],x),st[++t][0]=x;
}
inline void get_st()
{
	Log[1]=0;
	for (RG int i=2;i<=t;i++) Log[i]=Log[i>>1]+1;
	for (RG int i=1;i<=Log[t];i++)
	    for (RG int j=1;j<=t-(1<<i)+1;j++) 
	        st[j][i]=dep[st[j][i-1]]<dep[st[j+(1<<(i-1))][i-1]]?st[j][i-1]:st[j+(1<<(i-1))][i-1];
}
inline int get_lca(int x,int y)
{
	x=pre[x],y=pre[y];
	if (x>y) std::swap(x,y);
	RG int k=Log[y-x+1];
	return std::max(1,dep[st[x][k]]<dep[st[y-(1<<k)+1][k]]?st[x][k]:st[y-(1<<k)+1][k]);
}
struct Segment_Tree
{
	int segnum=0;
	struct treenode{int lson,rson,fi,la; ll sum,ans; } tree[MAXN<<4];
	#define lft tree[x].lson
	#define rht tree[x].rson
	inline void up(int x)
	{
	    RG int lca=get_lca(tree[lft].la,tree[rht].fi);
	    //cout<<lft<<" "<<rht<<" "<<lca<<endl;
		tree[x].ans=tree[lft].ans+tree[rht].ans-dep[lca];
		tree[x].fi=tree[lft].fi?tree[lft].fi:tree[rht].fi;
		tree[x].la=tree[rht].la?tree[rht].la:tree[lft].la;
	}
	inline void change(int &x,int p,int v,int L,int R)
	{
		if (!x) x=++segnum;
		if (L==R)
		{
			tree[x].sum+=v;
			tree[x].ans=tree[x].sum?dep[p]:0;
			tree[x].la=tree[x].fi=tree[x].sum?p:0;
			return;
		}
		RG int mid=(L+R)>>1;
		if (dfn[p]<=mid) change(lft,p,v,L,mid);
		else change(rht,p,v,mid+1,R);
		up(x);
	}
	inline ll query(int x)
	{ 
	    RG int lca=get_lca(tree[x].la,tree[x].fi);
	    return tree[x].ans-dep[lca];
	}
	inline void merge(int &x,int v,int L,int R)
	{
		if (!x||!v) { x|=v; return; }
		if (L==R)
		{
			tree[x].sum+=tree[v].sum;
			tree[x].ans|=tree[v].ans;
			tree[x].la|=tree[v].la;
			tree[x].fi|=tree[v].fi;
			return;
		}
		RG int mid=(L+R)>>1;
		merge(lft,tree[v].lson,L,mid);
		merge(rht,tree[v].rson,mid+1,R);
		up(x);
	}
} segment;
inline void solve(int x)
{
	for (RG int i=0;i<e[x].size();i++)
	    if (e[x][i]!=fa[x]) solve(e[x][i]);
	
	for (RG int i=0;i<Del[x].size();i++) segment.change(rt[x],Del[x][i],-1,1,n);
	ans+=segment.query(rt[x]);
	//cout<<ans<<endl;
	if (fa[x]) segment.merge(rt[fa[x]],rt[x],1,n);
}
int main()
{
	//freopen("a.in","r",stdin);
	n=read(),m=read();
	for (RG int i=1;i<n;i++)
	{
		int u=read(),v=read();
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dep[0]=-1;
	get_dfn(1,0);
	get_st();
	for (RG int i=1;i<=m;i++) 
	{
		RG int u=read(),v=read(),lca=get_lca(u,v);
		//cout<<lca<<endl;
		segment.change(rt[u],v,1,1,n); segment.change(rt[v],u,1,1,n); 
		segment.change(rt[v],v,1,1,n); segment.change(rt[u],u,1,1,n); 
		//segment.change(rt[lca],u,-1,1,n); segment.change(rt[lca],v,-1,1,n);
		//if (fa[lca]) segment.change(rt[fa[lca]],u,-1,1,n),segment.change(rt[fa[lca]],v,-1,1,n);
		//cout<<lca<<endl;
		Del[lca].push_back(u),Del[lca].push_back(v);
		if (fa[lca]) Del[fa[lca]].push_back(u),Del[fa[lca]].push_back(v);
	}
	solve(1);
	printf("%lld\n",ans>>1);
	return 0;
}
/*
12 4
1 2
2 3
2 4
4 5
1 6
6 7
6 8
6 9
9 10
9 11
1 12

4 12
8 10
7 11
3 10
*/

还附加了一个小样例,然而样例输出咕了。

时间限制:C/C++ 6000MS,其他语言 12000MS 内存限制:C/C++ 128MB,其他语言 256MB 难度:NOI/NOI+ 出题人: 描述 阿米巴和小强是好朋友。 阿米巴和小强在大海旁边看海水的波涛。小强第一次面对如此汹涌的海潮,他兴奋地叫个不停。而阿米巴则很淡定,他回想起曾经的那些日子,事业的起伏,情感的挫折……总之今天的风浪和曾经经历的那些风雨比起来,简直什么都不算。 于是,这对好朋友不可避免地产生了分歧。为了论证自己的观点,小强建立了一个模型。他海面抽象成一个1到N的排列P[1…N]。定义波动强度等于相邻两项的差的绝对值的和,即: L = | P2 – P1 | + | P3 – P2 | + … + | PN – PN-1 | 给你一个N和M,问:随机一个1…N的排列,它的波动强度不小于M的概率有多大? 答案请保留小数点后K位输出,四舍五入。 输入输出格式 输入格式: 输入文件wavel.in的第一行包含三个整数N, M和K,分别表示排列的长度,波动强度,输出位数。 输出格式: 输出文件wavel.out包含一个小数点后K位的实数。 输入输出样例 输入样例#1: 3 3 3 输出样例#1: 0.667 说明 N = 3的排列有6个:123,132,213,231,312,321;他们的波动强度分别为2,3,3,3,3,2。所以,波动强度不小于3的概率是4/6,即0.667。 你也可以通过下面的代码来验证这个概率: int a[3]={0,1,2},s=0,n=3; for (int i=0;i<1000000;i++){ random_shuffle(a,a+n); int t=0; for (int j=0;j=3) s++; } printf("%.3f\n",s/1000000.0); 【数据规模】 对于30%的数据,N ≤ 10。 对于另外30%的数据,K ≤ 3。 对于另外30%的数据,K ≤ 8。 对于另外10%的数据,N ≤ 50。 对于100%的数据,N ≤ 100,K ≤ 30,0 ≤ M ≤ 2147483647。 用例输入 1 用例输出 1 来源 ZJOI2012 省选 NOIP ZJOI 高级时间限制:C/C++ 6000MS,其他语言 12000MS 内存限制:C/C++ 128MB,其他语言 256MB 难度:NOI/NOI+ 出题人: 描述 阿米巴和小强是好朋友。 阿米巴和小强在大海旁边看海水的波涛。小强第一次面对如此汹涌的海潮,他兴奋地叫个不停。而阿米巴则很淡定,他回想起曾经的那些日子,事业的起伏,情感的挫折……总之今天的风浪和曾经经历的那些风雨比起来,简直什么都不算。 于是,这对好朋友不可避免地产生了分歧。为了论证自己的观点,小强建立了一个模型。他海面抽象成一个1到N的排列P[1…N]。定义波动强度等于相邻两项的差的绝对值的和,即: L = | P2 – P1 | + | P3 – P2 | + … + | PN – PN-1 | 给你一个N和M,问:随机一个1…N的排列,它的波动强度不小于M的概率有多大? 答案请保留小数点后K位输出,四舍五入。 输入输出格式 输入格式: 输入文件wavel.in的第一行包含三个整数N, M和K,分别表示排列的长度,波动强度,输出位数。 输出格式: 输出文件wavel.out包含一个小数点后K位的实数。 输入输出样例 输入样例#1: 3 3 3 输出样例#1: 0.667 说明 N = 3的排列有6个:123,132,213,231,312,321;他们的波动强度分别为2,3,3,3,3,2。所以,波动强度不小于3的概率是4/6,即0.667。 你也可以通过下面的代码来验证这个概率: int a[3]={0,1,2},s=0,n=3; for (int i=0;i<1000000;i++){ random_shuffle(a,a+n); int t=0; for (int j=0;j=3) s++; } printf("%.3f\n",s/1000000.0); 【数据规模】 对于30%的数据,N ≤ 10。 对于另外30%的数据,K ≤ 3。 对于另外30%的数据,K ≤ 8。 对于另外10%的数据,N ≤ 50。 对于100%的数据,N ≤ 100,K ≤ 30,0 ≤ M ≤ 2147483647。 用例输入 1 用例输出 1 来源 ZJOI2012 省选 NOIP ZJOI 高级
最新发布
10-30
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值