ZOJ_3231 Apple Transportation(树形DP || 最小费用流)

Apple Transportation

Time Limit: 1000 ms
Memory Limit: 32768 KB
Problem Description

There’s a big apple tree in the forest. In the tree there are N nodes (numbered from 0 to N - 1), and the nodes are connected by branches. On each node of the tree, there is a squirrel. In the autumn, some apples will grow on the nodes. After all apples are ripe, each squirrel will collect all the apples of their own node and store them. For the demand to be harmonic, they decide to redistribute the apples to minimize the variance (please refer to the hint) of apples in all nodes. Obviously, an apple cannot be divided into several parts. To reach this goal, some transportation should be taken. The cost of transporting x apples from node u to node v is x * distance (node u, node v). Now given the current amount of apples of each node and the structure of the apple tree, you should help the squirrels to find the minimal cost to redistribute the apples.

Input

Input consists of multiple test cases (less than 80 cases)!

For each test case, the first line contains an integer N (1 <= N <= 100), which is the number of the nodes in the tree.

The following line contains N integers a0,a1,…,aN-1 (0 <= ai <= 10000), representing the amount of the i-th node’s apples.

The following N - 1 lines each contain three integers u, v, c (0 <= u,v <= N - 1, 0 <= c <= 1000), which means node u and node v are connected by a branch, the length of the branch is c.

There is a blank line between consecutive cases.

Output

For each case output the minimal total transportation cost. The minimal cost is guaranteed to be less than 231.

Sample Input

3
1 2 3
0 1 1
0 2 1

3
1 3 3
0 1 3
0 2 4

2
1 2
0 1 1

Sample Output

1
3
0

题意

有一棵树,有n个节点,每个节点上有ai个物品,物品可以移动,每条边移动单位物品的代价为ci,希望移动后,节点上物品的方差尽量小,求最小代价。

题解:

设物品总和为 s u m sum sum, a v e = s u m / n ave=sum/n ave=sum/n, n u m = s u m num=sum%n num=sum;方差尽量小,物品应尽量平均分配,每个节点的物品数量都为 a v e ave ave a v e + 1 ave+1 ave+1

思路一:树形DP
对于每个节点来说,他的物品的移动一定是经过其父节点的,所以我们在DP时,只需要考虑将物品移动至其父节点的代价,如果需要从外部移入,则同样只需考虑从其父节点移入的代价。

设数组 d p [ i ] [ j ] dp[i][j] dp[i][j],代表考虑到节点u及其子树,存在 j j j个物品数为 a v e + 1 ave+1 ave+1的点的最小花费。
设当前处于节点u(以下u的子树都是指当前已DFS完成的部分),那么初始化只考虑当前点,然后DFS合并子树。设数组 s z sz sz代表结点所在子树的数量,c为节点到其父节点的距离, v v v代表正在处理的子树, u ′ u' u代表合并了子树 v v v的,则有:
d p [ u ′ ] [ j + k ] = m i n ( d p [ u ] [ j ] + d p [ v ] [ k ] + p n u m ∗ c ) dp[u'][j+k] = min(dp[u][j]+dp[v][k]+pnum*c) dp[u][j+k]=min(dp[u][j]+dp[v][k]+pnumc);
其中 p n u m = a b s ( a v e ∗ ( s z [ u ] + s z [ v ] ) + j + k − a [ u ] − a [ v ] ) − a b s ( a v e ∗ s z [ u ] + j − a [ u ] ) pnum = abs(ave*(sz[u]+sz[v])+j+k-a[u]-a[v])-abs(ave*sz[u]+j-a[u]) pnum=abs(ave(sz[u]+sz[v])+j+ka[u]a[v])abs(avesz[u]+ja[u])
(重申:此时a[u]和sz[u]的信息只是已DFS完成部分的信息)。u的子树中共有j+k个为ave+1的点,那么总共移出到其父节点(从父节点移入)的物品数量为 a b s ( a v e ∗ ( s z [ u ] + s z [ v ] ) + j + k − a [ u ] − a [ v ] ) abs(ave*(sz[u]+sz[v])+j+k-a[u]-a[v]) abs(ave(sz[u]+sz[v])+j+ka[u]a[v]),但 d p [ u ] [ j ] dp[u][j] dp[u][j]已经统计过一部分物品移出(移入)的代价,所以需要将其减去 a b s ( a v e ∗ s z [ u ] + j − a [ u ] ) abs(ave*sz[u]+j-a[u]) abs(avesz[u]+ja[u])
同时因为在合并子树v时,dp[u]的值会被多次访问和更新,所以需要另外设数组记录更新的值,待所有值计算完成后,再赋给dp[u].

思路二:最小费用流
num个数量为ave+1的点,可以将n个点连一条边,i->po,容量为1,费用为0;点po到汇点t连容量为num,费用为0的边。这样num个为+1的问题就解决了。
建图:
对于每个点连源点S到点i,容量为a[i],费用为0,点i到汇点T,容量为ave,费用为0;
对于每个点连i到特殊点po,容量为1,费用为0;po到汇点T,容量为num,费用为0;
将树的边连接,容量为INF,费用为ci,双向边。

DP代码:

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctype.h>
#include<cstring>
#include<set>
#include<queue>
#include<stack>
#include<iterator>
#define dbg(x) cout<<#x<<" = "<<x<<endl;
#define INF 0x3f3f3f3f
#define eps 1e-6
 
using namespace std;
typedef long long LL;   
typedef pair<int, int> P;
const int maxn = 120;
const int mod = 1000000007;
struct node{
    int to, w;
    node(){}
    node(int a, int b):to(a),w(b){}
};
int ave, sum, yu, a[maxn], sz[maxn];
LL dp[maxn][maxn], dps[maxn][maxn];
vector<node> g[maxn];
void dfs(int u, int fa, LL c);

int main()
{
    int n, i, j, k;
    while(~scanf("%d", &n))
    {
        sum = 0;
        for(i=0;i<n;i++)
        {
            scanf("%d", &a[i]);
            sum += a[i];
            g[i].clear();
        }
        for(i=1;i<n;i++){
            int c;
            scanf("%d %d %d", &j, &k, &c);
            g[j].push_back(node(k, c));
            g[k].push_back(node(j, c));
        }
        ave = sum / n, yu = sum%n;
        dfs(0, -1, 0LL);
        printf("%lld\n", dp[0][yu]);
    }
    return 0;
}

void dfs(int u, int fa, LL c)
{
    sz[u] = 1;
    for(int i=0;i<=min(sz[u], yu);i++)
        dp[u][i] = abs(ave+i-a[u])*c;
    for(int i=0;i<g[u].size();i++)
        if(g[u][i].to != fa)
        {
            int v = g[u][i].to;
            dfs(v, u, (LL)g[u][i].w);
            //因为dp数组后面的会依赖前面的结果,不能直接更新
            for(int j=0;j<=min(sz[u]+sz[v], yu);j++)
                dps[u][j] = 1e16;
            for(int j=0;j<=sz[u];j++)
                for(int k=0;k<=sz[v];k++)
                    if(j+k <= yu)
                    {
                        int num1 = abs(ave*sz[u]+j-a[u]);
                        int num2 = abs(ave*(sz[u]+sz[v])+j+k-a[u]-a[v]);
                        LL sum = dp[u][j]-num1*c + num2*c + dp[v][k];
                        dps[u][j+k] = min(dps[u][j+k], sum);
                    }
            for(int j=0;j<=min(sz[u]+sz[v], yu);j++)
                dp[u][j] = dps[u][j];
            a[u] += a[v];
            sz[u] += sz[v];
        }
}

费用流代码:

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<ctype.h>
#include<cstring>
#include<set>
#include<queue>
#include<stack>
#include<iterator>
#define dbg(x) cout<<#x<<" = "<<x<<endl;
#define INF 0x3f3f3f3f
#define eps 1e-6
 
using namespace std;
typedef long long LL;   
typedef pair<int, int> P;
const int maxn = 120;
const int mod = 1000000007;
struct node{
    int to, nex, cap, flow, cost;
}eg[1020];
int a[maxn];
int cnt, dis[maxn], vis[maxn], hd[maxn], pre[maxn];
void init();
bool spfa(int s, int t);
int min_cost_flow(int s, int t, int &cost);
void add(int fr, int to, int cap, int cost);

int main()
{
    int n, i, j, k, ed, sum;
    while(~scanf("%d", &n))
    {
        init();
        sum = 0, ed = n+2;
        for(i=1;i<=n;i++){
            scanf("%d", &a[i]);
            add(0, i, a[i], 0);
            sum += a[i];
        }
        int ave = sum/n, yu = sum%n;
        add(ed-1, ed, yu, 0);
        for(i=1;i<=n;i++){
            add(i, ed, ave, 0);
            add(i, ed-1, 1, 0);
        }
        for(i=1;i<n;i++){
            int c;
            scanf("%d %d %d", &j, &k, &c);
            j++, k++;
            add(j, k, INF, c);
            add(k, j, INF, c);
        }
        int ans;
        min_cost_flow(0, ed, ans);
        printf("%d\n", ans);
    }
    return 0;
}

void init()
{
    memset(hd, -1, sizeof(hd));
    cnt = 1;
}

void add(int fr, int to, int cap, int cost)
{
    eg[++cnt].to = to;
    eg[cnt].cap = cap;
    eg[cnt].flow = 0;
    eg[cnt].cost = cost;
    eg[cnt].nex = hd[fr];
    hd[fr] = cnt;
    eg[++cnt].to = fr;
    eg[cnt].cap = eg[cnt].flow = 0;
    eg[cnt].cost = -cost;
    eg[cnt].nex = hd[to];
    hd[to] = cnt;
}

bool spfa(int s, int t)
{
    for(int i=0;i<=t;i++)
        dis[i] = INF, pre[i] = -1, vis[i] = 0;
    dis[s] = 0;
    vis[s] = 1;
    queue<int> que;
    que.push(s);
    while(!que.empty())
    {
        int u = que.front();que.pop();
        vis[u] = 0;
        for(int i=hd[u];i!=-1;i=eg[i].nex)
        {
            node e = eg[i];
            if(e.cap>e.flow && dis[e.to] > dis[u]+e.cost)
            {
                dis[e.to] = dis[u]+e.cost;
                pre[e.to] = i;
                if(!vis[e.to])
                {
                    vis[e.to] = 1;
                    que.push(e.to);
                } 
            }
        }
    }
    return dis[t] != INF;
}

int min_cost_flow(int s, int t, int &cost)
{
    int res = 0, mi;
    cost = 0;
    while(spfa(s, t))
    {
        mi = INF;
        for(int i=pre[t];i!=-1;i=pre[eg[i^1].to])
            if(mi > eg[i].cap - eg[i].flow)
                mi = eg[i].cap - eg[i].flow;
        for(int i=pre[t];i!=-1;i=pre[eg[i^1].to])
        {
            eg[i].flow += mi;
            eg[i^1].flow -= mi;
            cost += eg[i].cost * mi;
        }
        res += mi;
    }
    return res;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值