区域赛树形DP真题题解


2021南京H
题目链接

题目大意:
给定一棵树,树上每个节点都有一些蝴蝶,数量为 a i a_i ai, 你可以去取走每个点的蝴蝶(从根节点1开始),但是当到达节点u后,点u的子节点会受到惊吓,会在 t i t_i ti秒后离开,问最多可以取走多少蝴蝶。

树形DP,状态的表示和转移有点不太好想,参考了其他博客,这里记录一下其做法

code:

#include<bits/stdc++.h>

using namespace std;
#define endl '\n'
#define ios std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

#define int long long
//head
const int N=2e5+10;
int h[N],e[N],ne[N],idx;
int a[N],t[N];
int f[N],g[N];
//g[u]表示不选u的孩子得到的最大值,即取完u立即返回
//f[u]表示以u为根的子树得到的最大值
//假设v是u的孩子,
//则g[u]+=f[v]-a[v];
//u没有子节点,f[u]=g[u],若有子节点,f[u]=max(f[u],g[u]+a[j]),表示向哪个儿子走
//如果t[j]==3,说明可以先走向一个其他儿子z,再回来取j,这时z的贡献变为了g[z]。
//因为g[u]中已经累加过f[z]-a[z],所以要减去该值,所以最终z的贡献为g[z]-(f[z]-a[z])
//因为j和z可能为同一点,因此求出该贡献的最大值与次大值,来更新f[u]
void add(int a,int b)
{
	e[idx]=b; ne[idx]=h[a]; h[a]=idx++;
}

void dfs(int u,int fa)
{
	f[u]=g[u]=a[u];
	int max1=-1e18,max2=-1e18;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa) continue;
		dfs(j,u);
		g[u]+=f[j]-a[j];
		int x=g[j]-(f[j]-a[j]);
		if(x>max1){
			max2=max1; max1=x;
		}
		else if(x>max2) max2=x;
	}
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa) continue;
		f[u]=max(f[u],g[u]+a[j]);
		if(t[j]==3){
			if(g[j]-(f[j]-a[j])==max1) f[u]=max(f[u],g[u]+max2+a[j]);
			else f[u]=max(f[u],g[u]+max1+a[j]);
		}
	}
}
void work()
{
	int n;
	cin>>n;
	idx=0;
	for(int i=1;i<=n;i++) h[i]=-1;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) cin>>t[i];
	for(int i=1;i<n;i++){
		int a,b;
		cin>>a>>b;
		add(a,b); add(b,a);
	}
	dfs(1,-1);
	cout<<f[1]<<endl;
}
signed main()
{
	ios;
	int t;
	cin>>t;
	while(t--)
	{
		work();
	}
	return 0;
}

2021上海
题目链接

题目大意:
给定一个n个节点的无向图(n为奇数),有n-1条边,(其实就是一棵树)。
把这些边分组,要求如下:

  1. 每组有且仅有两条边。
  2. 在同一组的两条边有一个公共点

求共有多少种方案.

思路:
考察以x为根节点的子树的方案数,其中fax的父节点.按子树的节点数为奇数、偶数分为两种情况,分别称为I型、II型子树.

II型子树有偶数个节点,奇数条边,再加上边(x,y2)即可两两配对;I型子树有奇数个节点,偶数条边,y1的子树中的边两两配对后,边(x,y1)未配对,可能需与边(fa,x)配对,是否需要用到边(fa,x)取决于x的子树中I型子树的数量的奇偶:①有偶数个时,所有I型子树的边(x, y i y_i yi)可两两配对;②有奇数个时,所有I型子树的边(x, y i y_i yi)两两配对,剩下的一条边与(fa,x)配对.

参考的博客
code:

#include<bits/stdc++.h>

using namespace std;
#define endl '\n'
#define ios std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define int long long

//head
const int N=2e5+10,mod=998244353;
int e[N],ne[N],h[N],idx;
int sz[N],f[N];
int g[N];
void add(int a,int b)
{
	e[idx]=b; ne[idx]=h[a]; h[a]=idx++;
}

void dfs(int u,int fa)
{
	sz[u]=1; f[u]=1;
	int cnt=0;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa) continue;
		dfs(j,u);
		sz[u]+=sz[j];
		f[u]=f[u]*f[j]%mod;
		if(sz[j]&1) cnt++;
	}
	if(cnt&1) f[u]=f[u]*g[cnt+1]%mod;
	else f[u]=f[u]*g[cnt]%mod;
}
signed main()
{
    ios;
	int n;
	cin>>n;
	memset(h,-1,sizeof h);
	g[0]=1;
	for(int i=2;i<=n;i+=2){
		g[i]=g[i-2]*(i-1)%mod;
	}
	for(int i=1;i<n;i++){
		int a,b;
		cin>>a>>b;
		add(a,b); add(b,a);
	}
	dfs(1,-1);

	cout<<f[1]<<endl;

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值