[WC2018]通道——边分治+虚树+树形DP

题目链接:

[WC2018]通道

 

题目大意:给出三棵n个节点结构不同的树,边有边权,要求找出一个点对(a,b)使三棵树上这两点的路径权值和最大,一条路径权值为路径上所有边的边权和。

我们按照部分分逐个分析有1、2、3棵树时的做法。

首先说一个结论,在下面讲解中能应用到:

对于一棵树T1的直径两端点为u,v,对于另一棵树T2的直径两端点为x,y,如果将两棵树合并(即将两棵树中的各一个点连边)那么新树的直径的两端点一定是u,v,x,y中的两个。

证明见树的直径及其性质与证明

一、一棵树

这个很好做吧,直接求树的直径就好了。

二、两棵树

我们假设答案点对是(a,b),那么ans(a,b)=dis1(a,b)+dis2(a,b)。其中dis1,dis2分别表示两棵树上的两点距离。

我们将第一棵树的答案拆开表示:ans(a,b)=dep1(a)+dep1(b)-2*dep1(lca)+dis2(a,b)。其中dep1为第一棵树中的该点深度。

可以发现dep1(lca)与a,b无关,我们对于第二棵树上的点x建立一个点x'与x相连,边权为dep1(x)。

这样忽略dep1(lca),答案就是Tree2中的直径。

现在计算dep1(lca)对答案的影响,我们可以对于Tree1树形DP,每个点存子树中所有点在Tree2中的形成的直径的两端点及直径长度,回溯时将每个子节点的答案合并到父节点上,这时Tree2中的直径减掉这个父节点深度*2即可更新答案。合并时利用上面讲到的结论六种情况枚举讨论,更新答案时因为要保证直径两端点不在这个父节点的同一棵子树内,所以有四种情况可以更新答案。这里Tree1中一个点子树中所有点在Tree2中形成的直径可以看作是这些点在Tree2上两两之间路径包含的所有点组成的树的直径,而合并时相当于在Tree2上将两个可能有交集的子树拼接到一起,上述结论依旧成立。

时间复杂度O(nlogn)。

三、三棵树

三棵树时答案为ans(a,b)=dis1(a,b)+dis2(a,b)+dis3(a,b)。

我们对于第一棵树进行边分治,将多叉树转二叉树,对于每次分治的联通块,以中心边为界将联通块分成两部分。(边分治具体实现参见边分治讲解

同时将第二棵树的答案拆开表示:ans(a,b)=d1(a)+d1(b)+val+dep2(a)+dep2(b)-2*dep2(lca)+dis3(a,b)。其中d1为该点到当前分治中心边的距离,val为分治中心边的长度。

同样按照两棵树时的做法,对于第三棵树上的点x建一个点x'与x相连,边权为d1(x)+dep2(x)。

每次对Tree1进行边分治时将分治联通块中的点在Tree2上建虚树,按照两棵树时的做法在虚树上树形DP。

因为还需要保证在Tree3中找到的直径的端点在Tree1中分别位于分治中心边的两端,所以每次边分治将分治中心一边的点标号为1,另一边的点标号为2。

在树形DP是每个点分别维护子树中标号为1/2的点组成的直径的两端点及直径长度,合并同样各种情况讨论一下并更新答案即可。

时间复杂度为O(nlogn^2),RMQ求LCA+基数排序可以将时间复杂度降为O(nlogn)。

注意:因为边权可能为0,所以求lca时不能比较真实深度(即带权深度)而要比较不带权深度。

这道题也可以用点分治来代替边分治,但边分治能将每次分治联通块中的点恰好分成两部分,而点分治对于分治中心的处理比较麻烦,所以建议写边分治。

亲测用树剖求LCA比用RMQ求LCA快,也不知道为什么...

调了好几天,也写了好几个版本的,最后还是都坚持调出来了,虽然很长,但还是都放上来,供读者选择。

其中edge_partation为第一棵树即边分治的树;virtual_tree为第二棵树即建虚树的树;value_tree为第三棵树即需要求直径的树。

RMQ求LCA+非递归树形DP

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define ll long long
#define pr pair
#define INF 1<<30
int n,m;
int cnt;
int x,y;
ll ans,z;
ll lty[100010];
int col[100010];
struct Miku
{
	int x;
	ll dep;
}t[400010];
namespace value_tree
{
	int tot;
	int dfn=0;
	ll d[100010];
	int s[100010];
	ll val[200010];
	int lg[200010];
	int to[200010];
	int head[100010];
	int next[200010];
	ll f[200010][19];
	inline void add(int x,int y,ll z)
	{
		next[++tot]=head[x];
		head[x]=tot;
		to[tot]=y;
		val[tot]=z;
	}
	void dfs(int x,int fa)
	{
		f[++dfn][0]=d[x];
		s[x]=dfn;
		lty[x]+=d[x];
		for(int i=head[x];i;i=next[i])
		{
			if(to[i]!=fa)
			{
				d[to[i]]=d[x]+val[i];
				dfs(to[i],x);
				f[++dfn][0]=d[x];
			}
		}
	}
	inline void ST()
	{
		for(int i=2;i<=dfn;i++)
		{
			lg[i]=lg[i>>1]+1;
		}
		for(int j=1;j<=18;j++)
		{
			for(int i=1;i+(1<y)
		{
			swap(x,y);
		}
		int len=lg[y-x+1];
		return min(f[x][len],f[y-(1<>1]+1;
		}
		for(int j=1;j<=18;j++)
		{
			for(int i=1;i+(1<y)
		{
			swap(x,y);
		}
		int len=lg[y-x+1];
		return mn(f[x][len],f[y-(1<0)
			{
				st[++top]=q[i];
			}
			else
			{
				top--;
				if(!top)continue;
				int fa=st[top];
				int x=st[top+1];
				ans=max(ans,max(merge(dp[x][0],dp[fa][1]),merge(dp[x][1],dp[fa][0]))+mid_edge-(d[fa]<<1));
				dp[fa][0]=dp[fa][0]+dp[x][0];
				dp[fa][1]=dp[fa][1]+dp[x][1];
			}
		}
	}
	inline void build(ll value)
	{
		mid_edge=value;
		for(int i=1;i<=cnt;i++)
		{
			vis[t[i].x]=1;
			dp[t[i].x][col[t[i].x]-1]=(miku){t[i].x,t[i].x,0};
			dp[t[i].x][(col[t[i].x]-1)^1]=(miku){0,0,0};
			q[i]=t[i].x;
			col[t[i].x]=0;
		}
		num=tot=cnt;
		sort(q+1,q+1+tot,cmp);
		for(int i=1;iq[400010];
	inline void add(int x,int y,ll z)
	{
		next[++tot]=head[x];
		head[x]=tot;
		to[tot]=y;
		val[tot]=z;
	}
	void dfs(int x,int fa)
	{
		for(int i=head[x];i;i=next[i])
		{
			if(to[i]!=fa)
			{
				q[x].push_back(make_pair(to[i],val[i]));
				dfs(to[i],x);
			}
		}
	}
	inline void rebuild()
	{
		tot=1;
		memset(head,0,sizeof(head));
		memset(val,0,sizeof(val));
		memset(next,0,sizeof(next));
		memset(to,0,sizeof(to));
		for(int i=1;i<=m;i++)
		{
			int len=q[i].size();
			if(len<=2)
			{
				for(int j=0;j>1]&&to[i]!=fa)
			{
				getroot(to[i],x,sum);
				size[x]+=size[to[i]];
				int mx_size=max(size[to[i]],sum-size[to[i]]);
				if(mx_size>1]&&to[i]!=fa)
			{
				dfs2(to[i],x,dep+val[i],opt);
			}
		}
	}
	void partation(int x,int sum)
	{
		num=INF;
		getroot(x,0,sum);
		if(num==INF)
		{
			return ;
		}
		int now=root;
		vis[now>>1]=1;
		cnt=0;
		dfs2(to[now],0,0ll,1);
		dfs2(to[now^1],0,0ll,2);
		for(int i=1;i<=cnt;i++)
		{
			lty[t[i].x]+=t[i].dep;
		}
		virtual_tree::build(val[now]);
		for(int i=1;i<=cnt;i++)
		{
			lty[t[i].x]-=t[i].dep;
		}
		int sz=size[to[now]];
		partation(to[now],sz);
		partation(to[now^1],sum-sz);
	}
}
int main()
{
	scanf("%d",&n);
	m=n;
	for(int i=1;i

RMQ求LCA+递归树形DP

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define ll long long
#define pr pair
#define INF 1<<30
int n,m;
int cnt;
int x,y;
ll ans,z;
ll lty[100010];
int col[100010];
struct Miku
{
	int x;
	ll dep;
}t[400010];
namespace value_tree
{
	int tot;
	int dfn=0;
	ll d[100010];
	int s[100010];
	ll val[200010];
	int lg[200010];
	int to[200010];
	int head[100010];
	int next[200010];
	ll f[200010][19];
	inline void add(int x,int y,ll z)
	{
		next[++tot]=head[x];
		head[x]=tot;
		to[tot]=y;
		val[tot]=z;
	}
	void dfs(int x,int fa)
	{
		f[++dfn][0]=d[x];
		s[x]=dfn;
		lty[x]+=d[x];
		for(int i=head[x];i;i=next[i])
		{
			if(to[i]!=fa)
			{
				d[to[i]]=d[x]+val[i];
				dfs(to[i],x);
				f[++dfn][0]=d[x];
			}
		}
	}
	inline void ST()
	{
		for(int i=2;i<=dfn;i++)
		{
			lg[i]=lg[i>>1]+1;
		}
		for(int j=1;j<=18;j++)
		{
			for(int i=1;i+(1<y)
		{
			swap(x,y);
		}
		int len=lg[y-x+1];
		return min(f[x][len],f[y-(1<q[100010];
	struct miku
	{
		int u,v;
		ll len;
		miku(){u=0,v=0,len=0;}
		miku (const int& U,const int& V){u=U,v=V,len=value_tree::dis(u,v);}
		miku (const int& U,const int& V,const ll& L){u=U,v=V,len=L;}
		friend bool operator <(miku a,miku b){return a.len>1]+1;
		}
		for(int j=1;j<=18;j++)
		{
			for(int i=1;i+(1<y)
		{
			swap(x,y);
		}
		int len=lg[y-x+1];
		return mn(f[x][len],f[y-(1<1&&dep[st[top-1]]>=dep[fa])
        {
            q[st[top-1]].push_back(st[top]);
            top--;
        }
        if(fa!=st[top])
        {
            q[fa].push_back(st[top]);
            st[top]=fa;
        }
        st[++top]=x;
	}
	inline ll merge(const miku& a,const miku& b)
	{
		if(a.u==0||b.u==0)return 0;
		return max(max(value_tree::dis(a.u,b.u),value_tree::dis(a.v,b.u)),max(value_tree::dis(a.u,b.v),value_tree::dis(a.v,b.v)));
	}
	void tree_dp(int x)
	{
		int len=q[x].size();
		for(int i=0;i1)
		{
			q[st[top-1]].push_back(st[top]);
			top--;
		}
		tree_dp(1);
	}
}
namespace edge_partation
{
	int tot;
	int num;
	int root;
	int to[800010];
	ll val[800010];
	int vis[400010];
	int next[800010];
	int head[400010];
	int size[400010];
	vectorq[400010];
	inline void add(int x,int y,ll z)
	{
		next[++tot]=head[x];
		head[x]=tot;
		to[tot]=y;
		val[tot]=z;
	}
	void dfs(int x,int fa)
	{
		for(int i=head[x];i;i=next[i])
		{
			if(to[i]!=fa)
			{
				q[x].push_back(make_pair(to[i],val[i]));
				dfs(to[i],x);
			}
		}
	}
	inline void rebuild()
	{
		tot=1;
		memset(head,0,sizeof(head));
		for(int i=1;i<=m;i++)
		{
			int len=q[i].size();
			if(len<=2)
			{
				for(int j=0;j>1]&&to[i]!=fa)
			{
				getroot(to[i],x,sum);
				size[x]+=size[to[i]];
				int mx_size=max(size[to[i]],sum-size[to[i]]);
				if(mx_size>1]&&to[i]!=fa)
			{
				dfs2(to[i],x,dep+val[i],opt);
			}
		}
	}
	void partation(int x,int sum)
	{
		num=INF;
		getroot(x,0,sum);
		if(num==INF)
		{
			return ;
		}
		int now=root;
		vis[now>>1]=1;
		cnt=0;
		dfs2(to[now],0,0ll,1);
		dfs2(to[now^1],0,0ll,2);
		for(int i=1;i<=cnt;i++)
		{
			lty[t[i].x]+=t[i].dep;
		}
		virtual_tree::build(val[now]);
		for(int i=1;i<=cnt;i++)
		{
			lty[t[i].x]-=t[i].dep;
		}
		int sz=size[to[now]];
		partation(to[now],sz);
		partation(to[now^1],sum-sz);
	}
}
int main()
{
	scanf("%d",&n);
	m=n;
	for(int i=1;i

树剖求LCA+递归树形DP

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define ll long long
#define pr pair
#define INF 1<<30
int n,m;
int cnt;
int x,y;
ll ans,z;
int lg[200010];
ll lty[100010];
int col[100010];
struct Miku
{
	int x;
	ll dep;
}t[400010];
namespace value_tree
{
	int tot;
	int dfn;
	ll d[100010];
	int s[100010];
	ll val[200010];
	int to[200010];
	int head[100010];
	int next[200010];
	ll f[200010][19];
	inline void add(int x,int y,ll z)
	{
		next[++tot]=head[x];
		head[x]=tot;
		to[tot]=y;
		val[tot]=z;
	}
	void dfs(int x,int fa)
	{
		f[++dfn][0]=d[x];
		s[x]=dfn;
		lty[x]+=d[x];
		for(int i=head[x];i;i=next[i])
		{
			if(to[i]!=fa)
			{
				d[to[i]]=d[x]+val[i];
				dfs(to[i],x);
				f[++dfn][0]=d[x];
			}
		}
	}
	inline void ST()
	{
		for(int i=2;i<=dfn;i++)
		{
			lg[i]=lg[i>>1]+1;
		}
		for(int j=1;j<=18;j++)
		{
			for(int i=1;i+(1<y)
		{
			swap(x,y);
		}
		int len=lg[y-x+1];
		return min(f[x][len],f[y-(1<q[100010];
	struct miku
	{
		int u,v;
		ll len;
		miku(){u=0,v=0,len=0;}
		miku (const int& U,const int& V){u=U,v=V,len=value_tree::dis(u,v);}
		miku (const int& U,const int& V,const int& L){u=U,v=V,len=L;}
		friend bool operator <(miku a,miku b){return a.lensize[son[x]])
				{
					son[x]=to[i];
				}
			}
		}
	}
	void dfs2(int x,int tp)
	{
		s[x]=++num;
		anc[x]=tp;
		if(son[x])
		{
			dfs2(son[x],tp);
		}
		for(int i=head[x];i;i=next[i])
		{
			if(to[i]!=f[x]&&to[i]!=son[x])
			{
				dfs2(to[i],to[i]);
			}
		}
	}
	inline int lca(int x,int y)
	{
		while(anc[x]!=anc[y])
		{
			if(dep[anc[x]]1&&dep[st[top-1]]>=dep[fa])
		{
			q[st[top-1]].push_back(st[top]);
			top--;
		}
		if(fa!=st[top])
		{
			q[fa].push_back(st[top]);
			st[top]=fa;
		}
		st[++top]=x;
	}
	inline ll merge(const miku& a,const miku& b)
	{
		if(a.u==0||b.u==0)return 0;
		return max(max(value_tree::dis(a.u,b.u),value_tree::dis(a.v,b.u)),max(value_tree::dis(a.u,b.v),value_tree::dis(a.v,b.v)));
	}
	void tree_dp(int x)
	{
		int len=q[x].size();
		for(int i=0;i1)
		{
			q[st[top-1]].push_back(st[top]);
			top--;
		}
		tree_dp(1);
	}
}
namespace edge_partation
{
	int tot;
	int num;
	int root;
	int to[800010];
	ll val[800010];
	int vis[400010];
	int next[800010];
	int head[400010];
	int size[400010];
	vectorq[400010];
	inline void add(int x,int y,ll z)
	{
		next[++tot]=head[x];
		head[x]=tot;
		to[tot]=y;
		val[tot]=z;
	}
	void dfs(int x,int fa)
	{
		for(int i=head[x];i;i=next[i])
		{
			if(to[i]!=fa)
			{
				q[x].push_back(make_pair(to[i],val[i]));
				dfs(to[i],x);
			}
		}
	}
	inline void rebuild()
	{
		tot=1;
		memset(head,0,sizeof(head));
		for(int i=1;i<=m;i++)
		{
			int len=q[i].size();
			if(len<=2)
			{
				for(int j=0;j>1]&&to[i]!=fa)
			{
				getroot(to[i],x,sum);
				size[x]+=size[to[i]];
				int mx_size=max(size[to[i]],sum-size[to[i]]);
				if(mx_size>1]&&to[i]!=fa)
			{
				dfs2(to[i],x,dep+val[i],opt);
			}
		}
	}
	void partation(int x,int sum)
	{
		num=INF;
		getroot(x,0,sum);
		if(num==INF)
		{
			return ;
		}
		int now=root;
		vis[now>>1]=1;
		cnt=0;
		dfs2(to[now],0,0ll,1);
		dfs2(to[now^1],0,0ll,2);
		for(int i=1;i<=cnt;i++)
		{
			lty[t[i].x]+=t[i].dep;
		}
		virtual_tree::build(val[now]);
		for(int i=1;i<=cnt;i++)
		{
			lty[t[i].x]-=t[i].dep;
		}
		int sz=size[to[now]];
		partation(to[now],sz);
		partation(to[now^1],sum-sz);
	}
}
int main()
{
	scanf("%d",&n);
	m=n;
	for(int i=1;i

转载于:https://www.cnblogs.com/Khada-Jhin/p/10166723.html

你可能感兴趣的:([WC2018]通道——边分治+虚树+树形DP)