复习树形 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\) 到其子树内距它最远的叶子节点与它的距离。
则有转移:
其中 \(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;
}