Living-Dream 系列笔记 第69期

Posted on 2024-07-30 15:21  _XOFqwq  阅读(3)  评论(0编辑  收藏  举报

复习树形 dp。

树形 dp 定义状态一般套路:令 \(dp_i\) 表示以 \(i\) 为子树的 xxx(要维护的信息),可以有多维,但一定会有这一维。

P2016 & P2014

请查阅往期笔记,此处不再赘述。

P2585

以前是分讨每个节点有几个儿子,然后分别转移。

其实不用分讨,直接将所有节点视作有两个儿子,初始时将它们的贡献设为 \(0\) 就不会有影响了。

然后记住本代码中递归建树是怎么写的。

code
#include<bits/stdc++.h>
using namespace std;

const int N=5e5+5;
string s;
int tot,tree[N<<1][2];
int dp[N][3],f[N][3];

int build(){
	int cur=++tot;
	if(s[cur]=='2')
		tree[cur][0]=build(),
		tree[cur][1]=build();
	else if(s[cur]=='1')
		tree[cur][0]=build();
	return cur; 
}
void dfs(int cur){
	if(!cur) return;
	dp[cur][0]=f[cur][0]=1;
	f[cur][1]=f[cur][2]=0;
	dp[cur][1]=dp[cur][2]=0;
	dfs(tree[cur][0]),dfs(tree[cur][1]);
	dp[cur][0]+=max(dp[tree[cur][0]][1]+dp[tree[cur][1]][2],dp[tree[cur][0]][2]+dp[tree[cur][1]][1]);
	dp[cur][1]+=max(dp[tree[cur][0]][0]+dp[tree[cur][1]][2],dp[tree[cur][0]][2]+dp[tree[cur][1]][0]);
	dp[cur][2]+=max(dp[tree[cur][0]][0]+dp[tree[cur][1]][1],dp[tree[cur][0]][1]+dp[tree[cur][1]][0]);
	f[cur][0]+=min(f[tree[cur][0]][1]+f[tree[cur][1]][2],f[tree[cur][0]][2]+f[tree[cur][1]][1]);
	f[cur][1]+=min(f[tree[cur][0]][0]+f[tree[cur][1]][2],f[tree[cur][0]][2]+f[tree[cur][1]][0]);
	f[cur][2]+=min(f[tree[cur][0]][0]+f[tree[cur][1]][1],f[tree[cur][0]][1]+f[tree[cur][1]][0]);
}

int main(){
	cin>>s,s="#"+s;
	memset(f,0x3f,sizeof f);
	dp[0][0]=dp[0][1]=dp[0][2]=0;
	f[0][0]=f[0][1]=f[0][2]=0;
	int t=build();
	dfs(1);
	int ans1=max({dp[1][0],dp[1][1],dp[1][2]});
	int ans2=min({f[1][0],f[1][1],f[1][2]});
	cout<<ans1<<' '<<ans2;
	return 0;
}

P1131

ZJOI 拿下二杀。

\(dp_i\) 表示在 \(i\) 的子树内使得其时态同步所需的最小道具使用次数。

答案显然为 \(\sum dp_i\)(每次使用道具只会改一条边,因此加上所有点)。

一个节点 \(i\) 要使用道具使其时态同步,当且仅当它的子节点 \(j\) 的子树满足时态同步,不然它怎么用道具都是无效的(记住 \(i\) 使用道具只能改变 \(i \to j\) 这一条边)。

\(dis_i\) 表示节点 \(i\) 到其子树内距它最远的叶子节点与它的距离。

则有转移:

\[dp_i=dis_i-dis_j-w \]

其中 \(w\) 表示边 \(i \to j\) 的边权。

\(dis_i\) 我们依然可以使用一个 dfs 维护(相当于一个辅助状态)。

初始状态都设 \(0\) 即可。

code
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=5e5+5;
int n,s;
struct E{ int v,w; };
vector<E> G[N<<1]; 
int dis[N],dp[N];

void pre(int cur,int fa){
	dis[cur]=0;
	for(auto i:G[cur]){
		if(i.v==fa) continue;
		pre(i.v,cur);
		dis[cur]=max(dis[cur],dis[i.v]+i.w); 
	}
}
void dfs(int cur,int fa){
	dp[cur]=0;
	for(auto i:G[cur]){
		if(i.v==fa) continue;
		dfs(i.v,cur);
		dp[cur]+=dis[cur]-dis[i.v]-i.w;
	}
}

signed main(){
	cin>>n>>s;
	for(int i=1,a,b,t;i<n;i++)
		cin>>a>>b>>t,
		G[a].push_back({b,t}),
		G[b].push_back({a,t});
	pre(s,0);
	dfs(s,0);
	int ans=0;
	for(int i=1;i<=n;i++) ans+=dp[i];
	cout<<ans; 
	return 0;
}

P1270

80 pts 做法:

与 P2014 同样的做即可。

不同之处:

  • 要将边权放到点权上。

  • 因为要往返,所以点权要乘 \(2\)

  • 只有叶子节点需要初始状态,具体见代码。

code
#include<bits/stdc++.h>
using namespace std;

const int N=2e4+5;
const int M=6e3+5;
int t,tot;
vector<int> G[N<<1];
int w[N],val[N];
int dp[N][M];

int build(){
	int w1,w2,cur=++tot;
	cin>>w1>>w2;
	w[cur]=w1*2;
	if(w2)
		val[cur]=w2;
	else{
		int l=build();
		G[cur].push_back(l);
		int r=build();
		G[cur].push_back(r);
	}
	return cur;
}
void dfs(int cur){
	if(val[cur])
		for(int i=w[cur];i<t;i++)
			dp[cur][i]=min((i-w[cur])/5,val[cur]);
	for(int i:G[cur]){
		dfs(i);
        for(int j=t-1;j>=w[cur];j--)
	       for(int k=0;k<=j-w[cur];k++)
	           dp[cur][j]=max(dp[cur][j],dp[i][k]+dp[cur][j-k]);
	}
}

int main(){
	ios::sync_with_stdio(0);
	cin>>t;
	//if(t==6000) cout<<457,exit(0);
	build();
	dfs(1);
	cout<<dp[1][t-1];
	return 0;
}

100 pts 做法:

待补。

维护 \(siz_i\) 表示 \(i\) 的子树内的耗时之和。

然后像 P2014 那样取 \(\min\) 缩减循环次数即可。

注意:

  • 叶子节点耗时也要加。

  • 叶子节点拿画的时间还要加。

  • \(dp\) 初始不能设极小值,因为不能拿负数张画,并且可以一张画也不拿。

code
#include<bits/stdc++.h>
using namespace std;

const int N=2e2+5;
const int M=6e3+5;
int t,tot;
vector<int> G[N<<1];
int w[N],val[N];
int dp[N][M],siz[N];

int build(){
	int w1,w2,cur=++tot;
	cin>>w1>>w2;
	w[cur]=w1*2;
	if(w2)
		val[cur]=w2;
	else{
		int l=build();
		G[cur].push_back(l);
		int r=build();
		G[cur].push_back(r);
	}
	return cur;
}
void dfs(int cur){
	int p=min(t-1,w[cur]);
	dp[cur][0]=dp[cur][p]=0;
	if(val[cur])
	{
		siz[cur]+=val[cur]*5; 
		for(int i=w[cur];i<max(siz[cur]+1,t);i++)
			dp[cur][i]=min((i-w[cur])/5,val[cur]);
	}
	siz[cur]+=w[cur];
	for(int i:G[cur]){
		dfs(i);
		siz[cur]+=siz[i];
		for(int j=min(siz[cur],t-1);j>=w[cur];j--)
			for(int k=0;k<=min(siz[i],j-w[cur]);k++)
				dp[cur][j]=max(dp[cur][j],dp[i][k]+dp[cur][j-k]);
	}
}

int main(){
	ios::sync_with_stdio(0);
//	memset(dp,0xcf,sizeof dp);
	cin>>t;
	build();
	dfs(1);
	cout<<dp[1][t-1];
	return 0;
}