HDU7458 旅行 题解

前言

感觉是非常优秀的题目,写一篇题解记录一下。

HDU-7458 旅行(on Vjudge)

题面

题目描述

有一棵 n 个结点的无根树,每个结点都有对应的类型 ci 和权重 wi ,你需要在这棵树上规划若干次旅行。

对于一次旅行,你将从一个树上的一个结点出发,沿着树上的边进行旅行,最终到达另一个和起点类型相同的结点。

你会进行很多次旅行,但你希望对于每个结点,在所有旅行路线中最多只会经过一次。

一次旅行的价值是起始点和终止点的权重和,你需要规划旅行的方案使得旅行的总权重和最大。

输入格式

第一行输入一个正整数 t (1t3) 代表数据组数。

对于每组数据:

第一行输入一个正整数 n (1n2×105),代表树的点数。

第二行输入 n 个正整数 ci (1cin) ,代表每个结点的类型。

第三行输入 n 个正整数 wi (1win) ,代表每个结点的权重。

接下来 n1 行每行两个正整数 ui,vi (1ui,vin,uivi) ,代表树上一条边,输入保证是一棵树。

输出格式

输出一行一个正整数代表答案。

输入输出样例

输入 #1

1
7
3 1 1 2 2 2 3
2 4 1 5 4 6 2
1 2
1 3
2 4 
2 5
3 6 
3 7

输出 #1

13

Hint

一种最优的旅行方案为:

旅行路线1:425 ,价值为 9

旅行路线2:137,价值为 4

价值总和为 13

题解

我把本题的限制看成了恰好每个点经过一遍,于是浪费了一个晚上,在此警示后人。

注意到如果根节点走过或没走过状态确定,可以划分到每棵子树,考虑是树形 DP。设状态 fx,0 表示子树 xx 未走过的最大权值, fx,1 表示子树 xx 已走过的最大权值。fx,0 的转移时容易的,因为不可能出现跨子树合并的贡献。

fx,0=vson(x)max{fx,0,fx,1}

接下来我们考虑 fx,1,显然我们需要在子树 x 内寻找一个与 x 类型相同的点,然后合并。或者找到两个不同子树类型相同的点,合并顺便经过 x。考虑选择 y 作为合并的节点之一的贡献。设 xy 路径上的点为 a1=x,a2,ak1,ak=y,则可以算出贡献为下面式子。

wx+wy+fy,0+i=1k1fai,0max{fai+1,0,fai+1,1}

这个式子没有对齐,并不方便计算,于是我们稍微修改一下求和顺序,把下标对齐。

wx+fx,0+wy+i=2kfai,0max{fai,0,fai,1}

选择两颗颜色相同的子树合并,就是 x 到这两个点的路径的贡献减去 fx,0,因为每一棵子树都恰好被按照 fx,0 的方式多算了一次,直接减掉即可。代码中化简了这个式子。

注意到 wx+fx,0 是确定的,对于某种颜色的转移其实只需要使 wy+i=2kfai,0max{fai,0,fai,1} 最大。我们不妨把某棵子树某种颜色的最大值的集合存进一个 map。注意到这个式子只与 xy 的路径有关,在搜索回溯的时候加入当前节点,并对这个子树已有状态的集合使用懒标记增加逐步自底向上求出。我们还要支持合并两个集合,因为我们要合并多个子树的信息。

注意到这个合并可以使用启发式合并,对于每种颜色,选择最大的两个合并或选择根合并,顺便更新最大值的集合,就可以求出 fi,1。时间复杂度 O(nlog2n)

代码

顺便积累一个写法,C++17 以后,可以利用下列方式访问 map 中所有元素。([c,p]c 为下标,p 为元素值)

for(auto [c,p]:g[b[v]])
#include <bits/stdc++.h>
using namespace std;
struct edge
{
	long long v,nxt;
}e[600000];
long long t,n,u,v,h[300000],b[300000],c[300000],w[300000],f[300000][2],mx[300000],tg[300000],cnt=0;
map<long long,long long>g[300000];
inline long long read()
{
	long long x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

void add_edge(long long u,long long v)
{
	e[++cnt].nxt=h[u];
	e[cnt].v=v;
	h[u]=cnt;
}

void dsu(long long x,long long fa)
{
	f[x][0]=f[x][1]=mx[x]=0;
    for(int i=h[x];i;i=e[i].nxt)
        if(e[i].v!=fa)dsu(e[i].v,x),f[x][0]+=mx[e[i].v];
    for(int i=h[x];i;i=e[i].nxt)
        if(e[i].v!=fa)
           {
           	long long v=e[i].v;
           	if(g[b[v]].count(c[x]))f[x][1]=max(f[x][1],g[b[v]][c[x]]+tg[b[v]]+f[x][0]+w[x]);
           	if(g[b[v]].size()>g[b[x]].size())swap(b[v],b[x]);
           	for(auto [c,p]:g[b[v]])
           	    if(g[b[x]].count(c))f[x][1]=max(f[x][1],p+tg[b[v]]+g[b[x]][c]+tg[b[x]]+f[x][0]);
           	for(auto [c,p]:g[b[v]])
           	    if(g[b[x]].count(c))g[b[x]][c]=max(g[b[x]][c],p+tg[b[v]]-tg[b[x]]);
           	    else g[b[x]][c]=p+tg[b[v]]-tg[b[x]];
		   }
	if(g[b[x]].count(c[x]))g[b[x]][c[x]]=max(g[b[x]][c[x]],w[x]-tg[b[x]]);
	else g[b[x]][c[x]]=w[x]-tg[b[x]];
    mx[x]=max(f[x][0],f[x][1]),tg[b[x]]+=f[x][0]-mx[x];
}

int main()
{
	t=read();
	while(t--)
	   {
	   	n=read();
	   	for(int i=1;i<=n;i++)h[i]=tg[i]=cnt=0,b[i]=i,g[i].clear();
	   	for(int i=1;i<=n;i++)c[i]=read();
	   	for(int i=1;i<=n;i++)w[i]=read();
	   	for(int i=1;i<=n-1;i++)u=read(),v=read(),add_edge(u,v),add_edge(v,u);
	   	dsu(1,0);
	   	printf("%lld\n",mx[1]);
	   }
	return 0;
}
posted @   w9095  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示