一、前言

  红书上面推荐的题目,在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[];
ll n;
void init() {
for(int i = ; i < n; i++) {
g[i].clear();
}
} //清空邻接表
void ins(int u, int v, ll c) {
g[u].push_back(make_pair(v, c));
} //插入一条边
ll a[]; //记录个点权重
ll cnt[], sum[];//记录子节点数目和子节点权重 void dfs1(int u, int f) {
cnt[u] = ;
sum[u] = a[u];
for(int i = ; 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[][];
inline ll ABS(ll x) {
if(x < ) x = -x;
return x;
}
inline void checkmin(ll &x, ll y) {
if(x == - || x > y) x = y;
}
void dfs(int u, int f) { if(cnt[u] == ) { //如果当前节点是叶子节点的话首先认为给他发1个或者0个指标都将有且仅有0的成本
dp[u][] = ;
dp[u][] = ;
return ;
}
ll d[][];
memset(d, -, sizeof(d));
d[][] = ;
int cc = ; //第cc个子节点给出的指标是J。应当认为CC用来保存每个合法节点的数量,于是重点是第几个合法节点。
//因而不适用I作为状态转移方程的指标
for(int i = ; 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 = ; j <= mcn; j++) { //枚举已经用掉的指标
if(d[cc][j] == -) continue; //如果当前已经用掉的指标不支持则继续
for(int k = ; k + j <= mcn && k <= cnt[v]; k++) { //枚举发出去的指标是K
if(dp[v][k] == -) continue; //如果子节点不接受K则继续枚举
int num = k * (pingj + ) + (cnt[v] - k) * pingj; //计算对于该指标下应发苹果数量
ll cost = (ll)ABS(sum[v] - num) * (ll)co + dp[v][k]; //计算对应的代价
checkmin(d[cc + ][k + j], cost + d[cc][j]); //更新最小值
}
}
cc++; //开始枚举下一个节点
}
for(int i = ; i <= mcn; i++) { //枚举每个可能得到的指标
if(d[cc][i] == -) continue; //如果发来该指标不合法则继续
checkmin(dp[u][i], d[cc][i]); //更新下最小值,扫描到最后一个节点之后的指标情况(应当认为是个背包) checkmin(dp[u][i + ], d[cc][i]);//这个步骤基本暗含了如果给根节点发指标的情况应该怎么处理(如果给当前根节点发了个指标的话) }
}
int main() {
while(~scanf("%d", &n)) {
init();
for(int i = ; i < n; i++) {
scanf("%lld", &a[i]);
}
for(int i = ; i < n - ; i++) {
int u, v;
ll c;
scanf("%d%d%lld", &u, &v, &c);
ins(u, v, c);
ins(v, u, c);
}
dfs1(, -);
pingj = sum[] / n;
mcn = sum[] - pingj * n;
memset(dp, -, sizeof(dp));
dfs(, -); printf("%lld\n", dp[][mcn]);
}
return ;
}
//我的代码:
#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=; vecp G[MAXN];
ll cnt[MAXN];
ll summ[MAXN];
ll arr[MAXN];
ll AVG,SHARE;
ll SUM=;
ll dp[MAXN][MAXN];
ll n;
void checkMin(ll &a,ll b)
{
if(a==-||a>b)a=b;
} void dfs_1(int now,int last)
{
int len=G[now].size();
cnt[now]=;
summ[now]=arr[now];
for(int i=;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]==)
{
dp[now][]=dp[now][]=;
return ;
}
int len=G[now].size();
int cc=;
ll d[][MAXN];
memset(d,-,sizeof(d));
d[][]=;
for(int i=;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=;j<=SHARE;++j)
{
if(d[cc&][j]==-)continue;
int c=cc&;
for(int k=;j+k<=SHARE&&k<=cnt[tar];++k)
{
if(dp[tar][k]==-)continue;
ll num=abs(AVG*cnt[tar]+k-summ[tar]);
ll cost=num*co+dp[tar][k];
checkMin(d[(cc+)&][k+j],cost+d[c][j]);
}
}
memset(d[cc&],-,sizeof(d[cc&]));
cc+=;
}
for(int i=;i<=SHARE;++i)
{
int c=cc&;
if(d[c][i]==-)continue;
checkMin(dp[now][i],d[c][i]);
checkMin(dp[now][i+],d[c][i]);
}
} void init()
{
memset(dp,-,sizeof(dp));
for(int i=;i<n;++i)
{
cin>>arr[i];
G[i].clear();
}
for(int i=;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(,-);
AVG = summ[]/n;
SHARE = summ[]%n;
dfs(,-);
cout<<dp[][SHARE]<<endl;
} int main()
{
cin.sync_with_stdio(false);
while(cin>>n)init(); return ;
}

PS: 在过了两天之后再一次重构这个代码发现实际上原作者在设计上有些小坑——checkMin函数在使用过程有可能会把-1赋值进去,于是特别判断下这一点会让代码变得优雅一些。

ZOJ 3231 Apple Transportation 树DP的更多相关文章

  1. URAL1018 Binary Apple Tree(树dp)

    组队赛的时候的一道题,那个时候想了一下感觉dp不怎么好写呀,现在写了出来,交上去过了,但是我觉得我还是应该WA的呀,因为总感觉dp的不对. #pragma warning(disable:4996) ...

  2. zoj3231 Apple Transportation(最大流)

    转载请注明出处: http://www.cnblogs.com/fraud/          ——by fraud Apple Transportation Time Limit: 1 Second ...

  3. POJ 2385 Apple Catching【DP】

    题意:2棵苹果树在T分钟内每分钟随机由某一棵苹果树掉下一个苹果,奶牛站在树#1下等着吃苹果,它最多愿意移动W次,问它最多能吃到几个苹果.思路:不妨按时间来思考,一给定时刻i,转移次数已知为j, 则它只 ...

  4. CF456D A Lot of Games (字典树+DP)

    D - A Lot of Games CF#260 Div2 D题 CF#260 Div1 B题 Codeforces Round #260 CF455B D. A Lot of Games time ...

  5. HDU4916 Count on the path(树dp??)

    这道题的题意其实有点略晦涩,定义f(a,b)为 minimum of vertices not on the path between vertices a and b. 其实它加一个minimum ...

  6. ZOJ3231 Apple Transportation(最小费用流)

    题目给你一棵苹果树,然后每个结点上有一定的苹果树,你要将苹果运输达到某个状态,使得均方差最小. 将苹果x个从a->b的花费是x*w,w是边权. 当时比赛的时候想的就是,最后达到的状态一定是sum ...

  7. Codeforces 219D. Choosing Capital for Treeland (树dp)

    题目链接:http://codeforces.com/contest/219/problem/D 树dp //#pragma comment(linker, "/STACK:10240000 ...

  8. HDU4276 The Ghost Blows Light SPFA&&树dp

    题目的介绍以及思路完全参考了下面的博客:http://blog.csdn.net/acm_cxlove/article/details/7964739 做这道题主要是为了加强自己对SPFA的代码的训练 ...

  9. Tsinsen A1219. 采矿(陈许旻) (树链剖分,线段树 + DP)

    [题目链接] http://www.tsinsen.com/A1219 [题意] 给定一棵树,a[u][i]代表u结点分配i人的收益,可以随时改变a[u],查询(u,v)代表在u子树的所有节点,在u- ...

随机推荐

  1. Java排序算法(二)

    java排序算法(二) 二.改进排序算法 2.1希尔排序 定义:希尔排序(ShellSort)是插入排序的一种.也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本.希尔排序是非稳定排序算法. ...

  2. .net 中的托管与非托管

    托管代码 托管代码就是Visual Basic .NET和C#编译器编译出来的代码.编译器把代码编译成中间语言(IL),而不是能直接在你的电脑上运行的机器码.中间语言被封装在一个叫程序集(assemb ...

  3. C#数字图像处理算法学习笔记(一)--C#图像处理的3中方法

    C#数字图像处理算法学习笔记(一)--C#图像处理的3中方法 Bitmap类:此类封装了GDI+中的一个位图,次位图有图形图像及其属性的像素数据组成.因此此类是用于处理像素数据定义的图形的对象.该类的 ...

  4. Java并发(六):并发策略

    通过多次优化实例来了解选择并发策略的正确姿势 通过模拟浏览器程序的渲染页面(Page-Rendering)功能,为了方便,假设HTML页面只会包含标签文本和图片以及URL; 第一个版本:串行加载页面元 ...

  5. mui的上拉下载和下拉刷新

    head部分(引入mui) <link href="./resources/css/mui.min.css" rel="stylesheet" /> ...

  6. 《C++面向对象程序设计》之变量的生存期

    <C++面向对象程序设计>之变量的生存期 静态生存期 (1)全局静态生存期:在函数体外声明,作用域从声明处开始到文件结尾处结束,称其有文件作用域,相当于全局变量 . (2)局部静态生存期: ...

  7. RStudio Server-0.99.902 (OpenLogic CentOS 7.2)

    RStudio Server-0.99.902 (OpenLogic CentOS 7.2) 0 评论 平台: CentOS 类型: 虚拟机镜像 软件包: r-3.2.3 rstudio-server ...

  8. IOS给图片增加水印(图片、文字)

    在网上发现很多人使用 CGContextDrawImage(context,CGRectMake(0,0,self.width,self.height),[image CGImage]); //原图  ...

  9. python剑指offer 包含min函数的栈

    题目描述 定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1)). # -*- coding:utf-8 -*- class Solution: def ...

  10. Shell重启Tomcat脚本

    #!/bin/bash echo -e "\n\n\n" #force kill flag,if equal [f] to force kill all flag="He ...