zoj2770(差分约束)火烧连营

来源:互联网 发布:转行程序员基础书籍 编辑:程序博客网 时间:2024/04/26 08:27

差分约束详细解释见电子书

4.13 火烧连营(Burn the Linked Camp)

题目来源:
ZOJ Monthly, October 2006, ZOJ2770
题目描述:
大家都知道,三国时期,蜀国刘备被吴国大都督陆逊打败了。刘备失败的原因是刘备的错误
决策。他把军队分成几十个大营,每个大营驻扎一队部队,又用树木编成栅栏,把大营连成一片,
称为连营。
让我们回到那个时代。陆逊派了很多密探,获得了他的敌人-刘备军队的信息。通过密探,
他知道刘备的军队已经分成几十个大营, 这些大营连成一片(一字排开), 这些大营从左到右用
1
n 编号。第 i个大营最多能容纳 Ci个士兵。而且通过观察刘备军队的动静,陆逊可以估计到从第 i

个大营到第 j 个大营至少有多少士兵。最后,陆逊必须估计出刘备最少有多少士兵,这样他才知

道要派多少士兵去烧刘备的大营。
输入描述:
输入文件中有多个测试数据。每个测试数据的第一行,有两个整数
n(0<n1000)m(0m
10000)。第二行,有n 个整数 C1Cn。接下来有m 行,每行有 3个整数 i, j, k(0<ijn, 0
k<231),表示从第i 个大营到第 j个大营至少有 k 个士兵。
输出描述:
对每个测试数据,输出一个整数,占一行,为陆逊估计出刘备军队至少有多少士兵。然而,
陆逊的估计可能不是很精确,如果不能很精确地估计出来,输出
"Bad Estimations"
样例输入: 样例输出:
3 2
1000 2000 1000
1 2 1100
2 3 1300
3 1
100 200 300
2 3 600
1300
Bad Estimations
分析:
以样例输入中第
1 个测试数据为例解释差分约束系统的构造及求解。其数学模型为:设3
军营的人数分别为
A1A2A3,容量为C1C2C3,前 n个军营的总人数为 Sn,则有以下不
等式组:
1) 根据第 i个大营到第 j 个大营士兵总数至少有k 个,得不等式组(1)
S2 – S0 >= 1100,等价于:S0 – S2 <= -1100
S3 – S1 >= 1300
,等价于: S1 – S3 <= -1300
2)
又根据实际情况,第 i 个大营到第 j 个大营的士兵总数不超过这些兵营容量之和,设d[i]
i 个大营容量总和,得不等式组(2)
S2 – S0 <= d[2] – d[0] = 3000
S3 – S1 <= d[3] – d[1] = 3000
3)
每个兵营实际人数不超过容量,得不等式组(3)
A1 <= 1000,等价于: S1 – S0 <= 1000
A2 <= 2000
,等价于: S2 – S1 <= 2000
A3 <= 1000
,等价于: S3 – S2 <= 1000
4)
另外由 Ai>=0,又得到不等式组(4)
S0 – S1 <= 0
S1 – S2 <= 0
S2 – S3 <= 0
本题要求的是 A1 + A2 + A3的最小值,即 S3 – S0 的最小值。
有向网的构造:首先每个
Si 对应到有向网中的一个顶点Vi;然后对上述 4个不等式组中的每
一个不等式:
Si – Sj <= c,转化成从Sj Si的一条有向边,权值为 c。由不等式组(1)(2)可知,
存在
<Sj, Si-1><Si-1, Sj>双向边,只是权值不一样;由不等式组(3)(4)可知,存在<Si, Si+1>
<Si+1, Si>双向边,只是权值不一样。构造好的有向网如图4.29 所示。 

构造好网络之后,最终要求什么?要求的是S3 – S0 的最小值,即要求不等式:
S3 – S0 >= M
中的约束 M,并且M 取其最大值,转化成:
S0 – S3 <= -M
即求 S3 S0 的最短路径(最小值),长度为-M,求得-M-1300,即M 1300M的最大值)。
对样例输入中的第
2 个测试数据,对应差分系统中的不等式及构造的有向网如图4.30 所示。
为什么这个测试数据的输出为
"Bad Estimations"?在Bellman-Ford 算法中,执行完本身的2
重循环之后,还应该检查每条边<u,v>,判断一下:加入这条边是否会使得顶点v 的最短路径值再
缩短,即判断:
dist[u] + w(u,v) < dist[v]是否成立,如果成立,则说明存在从源点可达的负权值回
路。这时应该输出
"Bad Estimations"

//差分约束,又特么的数学建模//这个建模很经典啊,这个建模有点麻烦啊//最后建模后,一个推导成了n到0的最短路径//spfa+slf优化#include<cstdio>#include<iostream>#include<cstring>#include<queue>using namespace std;const int mn=2000,mm=42000;//mm开小了,re了我半天 struct Edge{    int to,w,next;} edges[mm];int head[mn],tot,n,m;long long dis[mn];long long sum[mn];void add(int u,int v,long long w){    edges[tot].w=w;    edges[tot].to=v;    edges[tot].next=head[u];    head[u]=tot++;}int cnt[mn];//记录进队列的次数bool inq[mn];//标志是否在队列中deque<int> q;bool spfa(){    //计算n的单源点最短路    memset(cnt,0,sizeof(cnt));    memset(dis,6,sizeof(dis));    memset(inq,false,sizeof(inq));    dis[n]=0,cnt[n]=inq[n]=true;    q.clear();    q.push_back(n);    while(q.size())    {        int x=q.front();        q.pop_front();        inq[x]=false;        for(int i=head[x]; ~i; i=edges[i].next)        {            int v=edges[i].to;            if(dis[v]>dis[x]+edges[i].w)            {                dis[v]=dis[x]+edges[i].w;                if(!inq[v])                {                    if(++cnt[v]==n) return false;                    inq[v]=true;                    if(q.size()&&dis[v]<dis[q.front()])                        q.push_front(v);                    else                        q.push_back(v);                }            }        }    }    return true;}int main(){    int u,v,w;    while(~scanf("%d%d",&n,&m))    {        tot=0;        memset(head,-1,sizeof(head));        for(int i=1; i<=n; ++i)        {            scanf("%d",&u);            sum[i]=sum[i-1]+u;            add(i-1,i,u);            add(i,i-1,0);        }        for(int i=1; i<=m; ++i)        {            scanf("%d%d%d",&u,&v,&w);            add(v,u-1,-w);            add(u-1,v,sum[v]-sum[u-1]);        }        if(spfa()) printf("%lld\n",-dis[0]);        else puts("Bad Estimations");    }    return 0;}


原创粉丝点击