登神长阶!
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\) 要重新赋值最大值(因为初始值为 \(0\) 可能影响取 \(\min\),并且以前的答案是阶段答案,不能放到最终答案里去)并备份之前的答案(仍然要参与转移)。
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;
}