Living-Dream 系列笔记 第70期

Posted on 2024-07-31 13:56  _XOFqwq  阅读(4)  评论(0编辑  收藏  举报

P1272 & P1273

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

其中 P1273 我们学到了定义更好求解的状态(一般是转化为价值,如本题),再通过枚举求解最终答案

P8625

容易发现你选出的 \(S\) 一定是一个子树。

然后这题就变成最大子树和了。

关于最大子树和那题,请查阅往期笔记,此处不再赘述。

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

const int N=1e5+5;
int n,a[N];
int dp[N];
vector<int> G[N<<1];

void dfs(int cur,int fa){
	dp[cur]=a[cur];
	for(int i:G[cur]){
		if(i==fa) continue;
		dfs(i,cur);
		dp[cur]=max(dp[cur],dp[cur]+dp[i]);
	}
}

signed main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1,u,v;i<n;i++)
		cin>>u>>v,
		G[u].push_back(v),
		G[v].push_back(u);
	dfs(1,0);
	//cout<<dp[1]<<'\n';
	int ans=0;
	for(int i=1;i<=n;i++) ans=max(ans,dp[i]);
	cout<<ans;
	return 0;
} 

P4362

抽象题。不好评价。

拿到题目,先抽象出数学模型

则题目可以抽象为:

为树上的所有节点染色,共有 \(m\) 种颜色(钦定一号颜色为大头吃的),

若一条边的两个端点同色,则该边对难受值的贡献即为边权,否则贡献为 \(0\)

其中一号节点必须染一号颜色且必须恰有 \(k\) 个节点染一号颜色,其余颜色必须都染上至少一个节点,

求难受值的最小值,无解输出 \(-1\)

看到求最值 / 方案数又不要求输出方案的,考虑 dp。

四步法,首先设状态。

从朴素的角度考虑,设一个 \(dp\) 状态使其包含所有要求的信息

即:令 \(dp_{i,j,k}\) 表示以 \(i\) 为根的子树已经选了 \(j\) 个节点染一号颜色且节点 \(i\) 染了 \(k\) 号颜色的难受值的最小值。

定义了状态,我们就先分别考虑空间时间正确性这三方面上是否可行。

空间:\(O(n^3)\),可行。

时间:转移时需要枚举当前节点 \(cur\) 的子树内的 \(j,k\) 以及子节点 \(i\) 与其的子树内的 \(j,k\),大概 \(O(n^5)\),不可行。

正确性:显然可行。

综上,这个状态是不可行的。

但是,容易发现我们只关心一号颜色出现的次数,其他颜色只要出现过就行(即只关心重要部分)。

于是根据这一点,我们可以再次设出一个状态:

\(dp_{i,j,0/1}\) 表示以 \(i\) 为根的子树已经选了 \(j\) 个节点染一号颜色且节点 \(i\) 染了 / 没染一号颜色的难受值的最小值。

按照上述三个方面分析一遍,可以得出这个状态是可行的。

接着,我们确定答案。答案显然为 \(dp_{1,k,1}\)

然后,我们确定初始状态。初始状态显然为 \(dp_{i,0,0}=dp_{i,1,1}=0\)\(i\) 为树上任意节点)。

推转移方程。显然这是树上背包类的,且需要从 \(0/1\) 两种状态分别转移。

考虑一节点 \(cur\) 及其一子节点 \(i\)

  • \(cur\) 不染一号颜色:

    • \(i\) 染一号颜色,则显然无贡献。

    • \(i\) 不染一号颜色,则除非 \(m=2\),我都可以每层交替放置颜色使得 \(cur \to i\) 这条边无贡献;但若 \(m=2\),则无法交替,于是加上贡献(边权)即可。

  • \(cur\) 染一号颜色:

    • \(i\) 不染一号颜色,则加上贡献。

    • \(i\) 染一号颜色,则显然无贡献。

于是,我们得到状态转移方程:

\[\begin{cases} dp_{cur,j,0}=\min(dp_{cur,j,0},dp_{i,k,1}+dp_{cur,j-k,0},dp_{i,k,0}+dp_{cur,j-k,0}+f \times w)\\ dp_{cur,j,1}=\min(dp_{cur,j,1},dp_{i,k,1}+dp_{cur,j-k,1}+w,dp_{i,k,0}+dp_{cur,j-k,1}) \end{cases} \]

其中 \(w\) 为边权,\(j,k\) 为背包容量、选的物品数,\(f\) 为:

\[\begin{cases} f=1 \ \ \ \ \ \ \text{when}\ m=2\\ f=0 \ \ \ \ \ \ \text{when}\ m\neq2 \end{cases} \]

注意每次转移前 \(dp\) 要重新赋值最大值(因为当 \(m=2\) 时,前面的状态可能只在其子树染一号颜色节点,而并没有考虑后面的)并备份之前的答案(因为仍然要参与转移)。

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

const int N=3e2+5;
int n,m,k;
int siz[N],dp[N][N][2],tmp[N][2];
struct E{ int v,w; };
vector<E> G[N<<1];

void dfs(int cur,int fa){
	dp[cur][1][1]=0;
	dp[cur][0][0]=0;
	siz[cur]=1;
	for(auto i:G[cur]){
		if(i.v==fa) continue;
		dfs(i.v,cur);
		siz[cur]+=siz[i.v];
        memcpy(tmp,dp[cur],sizeof tmp);
        memset(dp[cur],0x3f,sizeof dp[cur]);
		for(int j=min(k,siz[cur]);j>=0;j--)
			for(int p=0;p<=min(j,siz[i.v]);p++)
				dp[cur][j][0]=min(dp[cur][j][0],dp[i.v][p][0]+tmp[j-p][0]+(m==2)*i.w),
				dp[cur][j][0]=min(dp[cur][j][0],dp[i.v][p][1]+tmp[j-p][0]),
				dp[cur][j][1]=min(dp[cur][j][1],dp[i.v][p][0]+tmp[j-p][1]),
				dp[cur][j][1]=min(dp[cur][j][1],dp[i.v][p][1]+tmp[j-p][1]+i.w);
	}
}

int main(){
	ios::sync_with_stdio(0);
	memset(dp,0x3f,sizeof dp);
	cin>>n>>m>>k;
	for(int i=1,u,v,w;i<n;i++)
		cin>>u>>v>>w,
		G[u].push_back({v,w}),
		G[v].push_back({u,w});
	if(n-k<m-1) cout<<-1,exit(0);
	dfs(1,0);
	cout<<dp[1][k][1];
	return 0;
}