ZOJ 3231 Apple Transportation 树DP

Posted 六花的邪王真眼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ZOJ 3231 Apple Transportation 树DP相关的知识,希望对你有一定的参考价值。

一、前言

  红书上面推荐的题目,在138页,提到了关键部分的题解,但是实际上他没提到的还有若干不太好实现的地方。尤其是在这道题是大家都拿网络流玩弄的大背景下,这个代码打不出来就相当的揪心了。。最后在牛客找到一个用来参考的代码,经过研究发现他的代码实际上实现的是那个比较简单的实现版本(二维但是使用背包来进行处理)。加了若干行注释强行理解之后,对最终复刻的版本做了一下滚动数组优化(之前该大佬在函数内部开105*105的大数组,我开的数字稍微大了一些就直接炸了)。

二、题意

  首先有一个树,生物学意义上的树和图论意义上的树,上面有N个节点,节点上有若干苹果,一群住在树上的松鼠想搞平均主义,将苹果尽量平均的分不到各个节点上——(意味着每个节点分到的苹果数量不是AVG,就是AVG+1个)于是要求你在这个要求之下求出所需花费的最小成本——(苹果数量*边的权重=成本)

三、题解

  第一个坑:假设没有AVG+1的树,应当如何进行分配?

设DP【i】意义为第i个节点及其子节点分配完所需要花费的成本,对于每个子树而言,实际上本身树上的苹果个数具体是多余AVG还是少于AVG并不重要——我们可以假设不论多还是少都可以向父节点进行周转,且最最终一定会达到平衡,因此我们不需要再更多的考虑谋一棵树上的苹果如果多了他去哪,如果少了他问谁要这种问题。

  第二个坑:对于有了AVG+1的节点,又有什么不同?

设dp[i][j]为给第i个节点分配j个AVG+1的指标,所需花费的最小成本。则应当认为当前节点i所具有的成本是“所有给子节点分配总大小为J的指标时的最小值”,当然这里如果使用强行枚举就太多了,所以在使用一发背包来解决这个最优化问题:大概类似于必须装满的01背包

  第三个坑:背包的滚动数组优化:具体看代码吧~分别帖他的代码和我的代码:

//他的代码
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <vector>
using namespace std;
typedef long long ll;
vector<pair<int, ll> > g[205];
ll n;
void init() {
    for(int i = 0; i < n; i++) {
        g[i].clear();
    }
}            //清空邻接表
void ins(int u, int v, ll c) {
    g[u].push_back(make_pair(v, c));
}            //插入一条边
ll a[205];    //记录个点权重
ll cnt[205], sum[205];//记录子节点数目和子节点权重

void dfs1(int u, int f) {
    cnt[u] = 1;
    sum[u] = a[u];
    for(int i = 0; i < g[u].size(); i++) {
        int ve = g[u][i].first, vc = g[u][i].second;
        if(ve == f) continue;
        dfs1(ve, u);
        cnt[u] += cnt[ve];
        sum[u] += sum[ve];
    }
}

ll pingj, mcn;        //保存平均数字和特殊指标
ll dp[105][105];
inline ll ABS(ll x) {
    if(x < 0) x = -x;
    return x;
}
inline void checkmin(ll &x, ll y) {
    if(x == -1 || x > y) x = y;
}
void dfs(int u, int f) {
 
    if(cnt[u] == 1) {    //如果当前节点是叶子节点的话首先认为给他发1个或者0个指标都将有且仅有0的成本
        dp[u][0] = 0;
        dp[u][1] = 0;
        return ;
    }
    ll d[105][105];
    memset(d, -1, sizeof(d));
    d[0][0] = 0;        
    int cc = 0;                //第cc个子节点给出的指标是J。应当认为CC用来保存每个合法节点的数量,于是重点是第几个合法节点。
                            //因而不适用I作为状态转移方程的指标
    for(int i = 0; i < g[u].size(); i++) {
        int v = g[u][i].first;
        ll co = g[u][i].second;
        if(v == f) continue;        //首先遍历所有的子节点,之后来处理背包相同的思路
        dfs(v, u);
        for(int j = 0; j <= mcn; j++) {        //枚举已经用掉的指标
            if(d[cc][j] == -1) continue;    //如果当前已经用掉的指标不支持则继续
            for(int k = 0; k + j <= mcn && k <= cnt[v]; k++) {        //枚举发出去的指标是K
                if(dp[v][k] == -1) continue;                        //如果子节点不接受K则继续枚举
                int num = k * (pingj + 1) + (cnt[v] - k) * pingj;    //计算对于该指标下应发苹果数量
                ll cost = (ll)ABS(sum[v] - num) * (ll)co + dp[v][k];    //计算对应的代价
                checkmin(d[cc + 1][k + j], cost + d[cc][j]);        //更新最小值
            }
        }
        cc++;                            //开始枚举下一个节点
    }
    for(int i = 0; i <= mcn; i++) {        //枚举每个可能得到的指标
        if(d[cc][i] == -1) continue;    //如果发来该指标不合法则继续
        checkmin(dp[u][i], d[cc][i]);    //更新下最小值,扫描到最后一个节点之后的指标情况(应当认为是个背包)
 
        checkmin(dp[u][i + 1], d[cc][i]);//这个步骤基本暗含了如果给根节点发指标的情况应该怎么处理(如果给当前根节点发了个指标的话)
                                        
    }
}
int main() {
    while(~scanf("%d", &n)) {
        init();
        for(int i = 0; i < n; i++) {
            scanf("%lld", &a[i]);
        }
        for(int i = 0; i < n - 1; i++) {
            int u, v;
            ll c;
            scanf("%d%d%lld", &u, &v, &c);
            ins(u, v, c);
            ins(v, u, c);
        }
        dfs1(0, -1);
        pingj = sum[0] / n;
        mcn = sum[0] - pingj * n;
        memset(dp, -1, sizeof(dp));
        dfs(0, -1);
 
        printf("%lld\n", dp[0][mcn]);
    }
    return 0;
}
//我的代码:
#include<iostream>
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<algorithm>
#include<set>
#include<map>
using namespace std;
#define veci vector<int> 

#define stai stack<int> 
#define ll long long 
#define pp pair<int,ll> 
#define vecp vector<pp>

const long long MAXN=233;

vecp G[MAXN];
ll cnt[MAXN];
ll summ[MAXN];
ll arr[MAXN];
ll AVG,SHARE;
ll SUM=0;
ll dp[MAXN][MAXN];
ll n;
void checkMin(ll &a,ll b)
{
    if(a==-1||a>b)a=b;
}

void dfs_1(int now,int last)
{
    int len=G[now].size();
    cnt[now]=1;
    summ[now]=arr[now];
    for(int i=0;i<len;++i)
    {
        int tar=G[now][i].first;
        if(tar==last)continue;
        dfs_1(tar,now);
        cnt[now]+=cnt[tar];
        summ[now]+=summ[tar];
    }
}

void dfs(int now,int last)
{
    if(cnt[now]==1)
    {
        dp[now][0]=dp[now][1]=0;
        return ;
    }
    int len=G[now].size();
    int cc=0;
    ll d[4][MAXN];
    memset(d,-1,sizeof(d));
    d[0][0]=0;
    for(int i=0;i<len;++i)
    {
        int tar=G[now][i].first;
        ll co=G[now][i].second;
        if(tar==last)continue;
        dfs(tar,now);
        for(int j=0;j<=SHARE;++j)
        {
            if(d[cc&1][j]==-1)continue;
            int c=cc&1;
            for(int k=0;j+k<=SHARE&&k<=cnt[tar];++k)
            {
                if(dp[tar][k]==-1)continue;
                ll num=abs(AVG*cnt[tar]+k-summ[tar]);
                ll cost=num*co+dp[tar][k];
                checkMin(d[(cc+1)&1][k+j],cost+d[c][j]);
            }
        }
        memset(d[cc&1],-1,sizeof(d[cc&1]));
        cc+=1;
    }
    for(int i=0;i<=SHARE;++i)
    {
        int c=cc&1;
        if(d[c][i]==-1)continue;
        checkMin(dp[now][i],d[c][i]);
        checkMin(dp[now][i+1],d[c][i]);
    }
}

void init()
{
    memset(dp,-1,sizeof(dp));
    for(int i=0;i<n;++i)
    {
        cin>>arr[i];
        G[i].clear();
    }
    for(int i=1;i<n;++i)
    {
        ll a,b,c;cin>>a>>b>>c;
        G[a].push_back(make_pair(b,c));
        G[b].push_back(make_pair(a,c));
    }dfs_1(0,-1);
    AVG = summ[0]/n;
    SHARE = summ[0]%n;
    dfs(0,-1);
    cout<<dp[0][SHARE]<<endl;
}

int main()
{
    cin.sync_with_stdio(false);
    while(cin>>n)init();
    
    
    return 0;
}

 

以上是关于ZOJ 3231 Apple Transportation 树DP的主要内容,如果未能解决你的问题,请参考以下文章

ds3231可以用ds3232代替吗

ds3231高精度时钟模块怎么用

研华 FWA-3231 单路E3平台

小吃(codevs 3231)

张高兴的 Windows 10 IoT 开发笔记:RTC 时钟模块 DS3231

HDU3231拓扑排序