P4629 SHOI2015 聚变反应炉
P4629 SHOI2015 聚变反应炉
树上背包+树形dp。
算是套娃题吗?
思路
看到数据考虑数据分治。
part1 贪心 \(c_i\leq 1\)
对于这种情况,我们考虑贪心的点亮。
手玩几组数据,发现只要先全部点亮 \(c_i=1\) 的点,都可以得到最优解。
若存在一个 \(c_i=1\) 的节点连接 \(1\) 的节点个数小于 \(d_i\),设个数为 \(w\),那么我们后选他可以减少 \(w\) 的能量。
若我们在中途的某一个时刻选择这个点,他也可以对答案减少 \(w\) 的能量(\(d_i\geq 1\),没激活的邻接点 \(-1\),激活的也可以 \(-1\))。
若这个点的 \(d_i\) 小于连接 \(1\) 的节点的个数,若我们需要花费能量激活的话,和上述情况同理;若不需要花费能量激活,此时的没有被激活的边又可以造成 \(-1\) 的贡献。
所以无论选择的顺序,只要先选择 \(c=1\) 的点,我们一定可以最小化答案。
至于等于 \(c=0\) 的点,吸收完所有 \(c=1\) 的点的贡献在激活一定是最优的。
part2 树形 dp
首先这题的状态不是很好设,因为要考虑儿子的贡献,父亲的贡献,很容易就混进去了。
我们不妨想着先解决父亲的贡献。
设 \(dp[u][0]\) 为 \(u\) 先点亮,且 \(u\) 子树内均被点亮的最小能量,\(dp[u][1]\) 为 \(u\) 的父亲比 \(u\) 先被点亮,\(u\) 的子树内再被点亮的最小能量。
显然这样子没有考虑儿子的传递的能量,我们是写不出转移方程的。
不如设 \(tmp[u][i][j]\) 为 \(u\) 的前 \(i\) 个儿子给 \(u\) 传递了 \(j\) 的能量,点亮前 \(i\) 个儿子的子树内的所有点的最小能量。
这里有转移:
不难发现我们这是一个树上背包,我们对其使用滚动数组优化。
对于 \(dp[u][0/1]\) 有转移:
令 \(sum=\sum_{v\in u.sons} c[v]\),\(fa\) 为 \(u\) 的父亲。
由于父亲的方程和 \(tmp\) 无关,可以每次新开一个 \(tmp\) 数组。
最后 \(dp[1][0]\) 就是答案喽。
CODE
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=2e5+5,maxm=3e3+5;
struct Edge
{
int tot;
int head[maxn];
struct edgenode{int to,nxt;}edge[maxn*2];
inline void add(int x,int y)
{
tot++;
edge[tot].to=y;
edge[tot].nxt=head[x];
head[x]=tot;
}
}T;
int n;
int d[maxn],c[maxn];
ll dp[maxm][2],tmp[2][maxm*5];
inline void solve()
{
ll ans=0;
for(int i=1;i<=n;i++)
{
if(c[i]==1)
{
ans+=d[i];d[i]=0;
for(int j=T.head[i];j;j=T.edge[j].nxt)
{
int v=T.edge[j].to;
d[v]--;
}
}
}
for(int i=1;i<=n;i++) if(d[i]>0) ans+=d[i];
printf("%lld",ans);
}
inline void dfs(int u,int f)//dp 部分
{
int sum=0;
for(int i=T.head[u];i;i=T.edge[i].nxt)
{
int v=T.edge[i].to;
if(v==f) continue;
dfs(v,u);sum+=c[v];
}
memset(tmp,0x3f,sizeof(tmp));
tmp[0][0]=0;
int cur=0;
for(int i=T.head[u];i;i=T.edge[i].nxt)
{
int v=T.edge[i].to;
if(v==f) continue;
cur^=1;
memset(tmp[cur],0x3f,sizeof(tmp[cur]));
for(int j=0;j<=sum-c[v];j++)
{
tmp[cur][j+c[v]]=min(tmp[cur][j+c[v]],tmp[cur^1][j]+dp[v][0]);
tmp[cur][j]=min(tmp[cur][j],tmp[cur^1][j]+dp[v][1]);
}
}
for(int i=0;i<=sum;i++)
{
dp[u][0]=min(dp[u][0],max(tmp[cur][i],tmp[cur][i]-i+d[u]));
dp[u][1]=min(dp[u][1],max(tmp[cur][i],tmp[cur][i]-i+d[u]-c[f]));
}
}
int main()
{
int mx=0;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&d[i]);
for(int i=1;i<=n;i++) scanf("%d",&c[i]),mx=max(c[i],mx);
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
T.add(x,y),T.add(y,x);
}
if(mx<=1) solve(),exit(0);
memset(dp,0x3f,sizeof(dp));
dfs(1,0);
printf("%lld",dp[1][0]);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现