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\) 个儿子的子树内的所有点的最小能量。

这里有转移:

\[tmp[u][i][j+c[v]]=\min(dp[u][i][j+c[v]],tmp[u][i-1][j]+dp[v][0])\\ tmp[u][i][j]=\min(dp[u][i][j],tmp[u][i-1][j]+dp[v][1]) \]

不难发现我们这是一个树上背包,我们对其使用滚动数组优化。

\[tmp[u][cur][j+c[v]]=\min(dp[u][cur][j+c[v]],tmp[u][cur\oplus 1][j]+dp[v][0])\\ tmp[u][cur][j]=\min(dp[u][cur][j],tmp[u][cur\oplus 1][j]+dp[v][1]) \]

对于 \(dp[u][0/1]\) 有转移:

\(sum=\sum_{v\in u.sons} c[v]\)\(fa\)\(u\) 的父亲。

\[dp[u][0]=\min_{i=0}^{sum} (dp[u][0],\max(tmp[i],tmp[i]-i+d[u]))\\ dp[u][1]=\min_{i=0}^{sum}(dp[u][0],\max(tmp[i],tmp[i]-i+d[u]-c[fa])) \]

由于父亲的方程和 \(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]);
}
posted @ 2024-06-07 13:44  彬彬冰激凌  阅读(1)  评论(0编辑  收藏  举报