由201809-4再卖菜问题深入探讨差分约束系统

试题编号:201809-4
试题名称:再卖菜
时间限制:1.0s
内存限制:256.0MB
问题描述:

问题描述

  在一条街上有n个卖菜的商店,按1至n的顺序排成一排,这些商店都卖一种蔬菜。
  第一天,每个商店都自己定了一个正整数的价格。店主们希望自己的菜价和其他商店的一致,第二天,每一家商店都会根据他自己和相邻商店的价格调整自己的价格。具体的,每家商店都会将第二天的菜价设置为自己和相邻商店第一天菜价的平均值(用去尾法取整)。
  注意,编号为1的商店只有一个相邻的商店2,编号为n的商店只有一个相邻的商店n-1,其他编号为i的商店有两个相邻的商店i-1和i+1。
  给定第二天各个商店的菜价,可能存在不同的符合要求的第一天的菜价,请找到符合要求的第一天菜价中字典序最小的一种。
  字典序大小的定义:对于两个不同的价格序列(a1, a2, ..., an)和(b1, b2, b3, ..., bn),若存在i (i>=1), 使得ai<bi,且对于所有j<i,aj=bj,则认为第一个序列的字典序小于第二个序列。

输入格式

  输入的第一行包含一个整数n,表示商店的数量。
  第二行包含n个正整数,依次表示每个商店第二天的菜价。

输出格式

  输出一行,包含n个正整数,依次表示每个商店第一天的菜价。

样例输入

8
2 2 1 3 4 9 10 13

样例输出

2 2 2 1 6 5 16 10

数据规模和约定

  对于30%的评测用例,2<=n<=5,第二天每个商店的菜价为不超过10的正整数;
  对于60%的评测用例,2<=n<=20,第二天每个商店的菜价为不超过100的正整数;
  对于所有评测用例,2<=n<=300,第二天每个商店的菜价为不超过100的正整数。
  请注意,以上都是给的第二天菜价的范围,第一天菜价可能会超过此范围。

对于这个问题,我们可以建立下面几个不等式:

假设n=5,则有:

a1* 2≤ x1+x2 ≤ a1* 2+1
a2* 3≤ x1+x2+x3 ≤ a2* 3+2
a3* 3≤ x2+x3+x4 ≤ a3* 3+2
a4* 3≤ x3+x4+x5 ≤ a4* 3+2
a5* 2≤ x4+x5 ≤ a5* 2+1
x1≥1
x2≥1
x3≥1
x4≥1
x5≥1
xi为第一天第i家店的售价,ai为第二天第i家店的售价。
对于其中的三元一次不等式,我们可以引入中间变量si=x0+x1+…+xi,这里的x0等于0;
即可将上面的第一个不等式转化成: a1* 2≤ s2-s0 ,a1* 2+1≤s0-s2.其它的也一样,不一一列举,全部转化成大于等于的形式,sj-si≥k,i到j建立一条权值为k的单向边,然后利用spfa得到最长路即可。

这里提到了一个差分约束的问题,现在我们就来具体解释一下什么是差分约束。

引例

首先, 给定n个变量和m个不等式,每个不等式形如 x[i] - x[j] <= a[k] (0 <= i, j < n, 0 <= k < m, a[k]已知),求 x[n-1] - x[0] 的最大值。例如当n = 4,m = 5,有下面这样一组不等式组,求x3 - x0的最大值。

观察x3 - x0的性质,我们如果可以通过不等式的两两相加加和得到形如 x3 - x0 <= Ti 的不等式,那么 min{ Ti | 0 <= i < c } 就是我们要求的x3 - x0的最大值。

就此问题,我们可以得到以下三个不等式:

      1.      (3)                       x3 - x0 <= 8

      2.      (2) + (5)              x3 - x0 <= 9

      3.      (1) + (4) + (5)     x3 - x0 <= 7

这里的T等于{8, 9, 7},所以min{ T } = 7,答案就是7。

通过计算我们可以发现,问题的答案也是7,这是巧合吗?

让我们来看另一个问题,这个问题描述相对简单,给定四个小岛以及小岛之间的有向距离,问从第0个岛到第3个岛的最短距离。如图一-1-2所示,箭头指向的线段代表两个小岛之间的有向边,蓝色数字代表距离权值。

这个问题就是经典的最短路问题。由于这个图比较简单,我们可以枚举所有的路线,发现总共三条路线,如下:

      1.       0 -> 3                       长度为8

      2.       0 -> 2 -> 3               长度为7+2 = 9

      3.       0 -> 1 -> 2 -> 3       长度为2 + 3 + 2 = 7

最短路为三条线路中的长度的最小值即7,所以最短路的长度就是7。这和上面的不等式有什么关系呢?

先来看看最短路求解的原理……

下面介绍三种经典的求解最短路径问题的算法

  • Dijkstra算法

      具体算法描述如下:对于图G = <V, E>,源点为s,d[i]表示s到i的最短路,visit[i]表示d[i]是否已经确定(布尔值)。

      1) 初始化 所有顶点 d[i] = INF, visit[i] = false,令d[s] = 0;

      2) 从所有visit[i]为false的顶点中找到一个d[i]值最小的,令x = i; 如果找不到,算法结束;

      3) 标记visit[x] = true, 更新和x直接相邻的所有顶点y的最短路: d[y] = min{ d[y], d[x] + w(x, y) }

     (第三步中如果y和x并不是直接相邻,则令w(x, y) = INF)

  • Bellman-Ford

       具体算法描述如下:对于图G = <V, E>,源点为s,d[i]表示s到i的最短路。

      1) 初始化 所有顶点 d[i] = INF, 令d[s] = 0,计数器 j = 0;

      2) 枚举每条边(u, v),如果d[u]不等于INF并且 d[u] + w(u, v) < d[v],则令d[v] = d[u] + w(u, v);

      3) 计数器j + +,当j = n - 1时算法结束,否则继续重复2)的步骤; 

      第2)步的一次更新称为边的“松弛”操作。

  • SPFA

       SPFA( Shortest Path Faster Algorithm )是基于Bellman-Ford的思想,采用先进先出(FIFO)队列进行优化的一个计算单源最短路的快速算法。

       SPFA算法利用一个先进先出的队列用来保存待松弛的结点,每次取出队首结点u,并且枚举从u出发的所有边(u, v),如果d[u] + w(u, v) < d[v],则更新d[v] = d[u] + w(u, v),然后判断v点在不在队列中,如果不在就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。 

下面是它的伪代码:

bool spfa(s) {
	for(i = 0; i < n; i++) {
		d[i] = (i == s) ? 0 : INF;
		inq[i] = (i == s);                  //inq[i]表示结点i是否在队列中,初始时只有s在队列中
		visitCount[i] = 0;
	}
	q.push( (d[s], s) );
	while( !q.empty() ) {
		(dist, u) = q.front();              //q.front()为FIFO队列的队列首元素
		q.pop();
		inq[u] = false;
		if( visitCount[u]++ > n ) {         //判断是否存在负权圈,如果存在,函数返回true;
			return true;
		}
		for (e = head[u]; e != INF; e = edge[e].next) {
			v = edge[e].v;
			w = edge[e].w;
			if(d[u] + w < d[v]) {           //和Dijkstra优先队列优化的算法很相似的松弛操作
				d[v] = d[u] + w;
				if ( !inq[v] ) {
					inq[v] = true;
					q.push( (d[v], v) );
				}
			}
		}
	}
	return false;
}

差分约束系统

介绍完最短路,回到之前提到的那个不等式组的问题上来,我们将它更加系统化。

        如若一个系统由n个变量和m个不等式组成,并且这m个不等式对应的系数矩阵中每一行有且仅有一个1和-1,其它的都为0,这样的系统称为差分约束( difference constraints )系统。

        然后继续回到单个不等式上来,观察 x[i] - x[j] <= a[k], 将这个不等式稍稍变形,将x[j]移到不等式右边,则有x[i] <= x[j] + a[k],然后我们令a[k] = w(j, i),再将不等式中的i和j变量替换掉,i = v, j = u,将x数组的名字改成d(以上都是等价变换,不会改变原有不等式的性质),则原先的不等式变成了以下形式:d[u] + w(u, v) >= d[v]。

        这时候联想到SPFA中的一个松弛操作:

if(d[u] + w(u, v) < d[v]) {        
    d[v] = d[u] + w(u, v);    
}

      对比上面的不等式,两个不等式的不等号正好相反,但是再仔细一想,其实它们的逻辑是一致的,因为SPFA的松弛操作是在满足小于的情况下进行松弛,力求达到d[u] + w(u, v) >= d[v],而我们之前令a[k] = w(j, i),所以我们可以将每个不等式转化成图上的有向边:

      对于每个不等式 x[i] - x[j] <= a[k],对结点 j 和 i 建立一条 j -> i的有向边,边权为a[k],求x[n-1] - x[0] 的最大值就是求 0 到n-1的最短路。

如果还没有完全理解,我们可以先来看一个简单的情况,如下三个不等式:

B - A <= c      (1)

C - B <= a      (2)

C - A <= b      (3)

      我们想要知道C - A的最大值,通过(1) + (2),可以得到 C - A <= a + c,所以这个问题其实就是求min{b, a+c}。将上面的三个不等式转化成下图:

我们发现min{b, a+c}正好对应了A到C的最短路,而这三个不等式就是著名的三角不等式。将三个不等式推广到m个,变量推广到n个,就变成了n个点m条边的最短路问题了。

现在,再回到201809-4再卖菜这道题。

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
#define N 3005
#define inf 0xffffffff 
int a[N],s[N];
int tot;
int head[N];
struct node
{
    int w,to,nt;
}g[10000];
 
void adeEdge(int u,int v,int w)
{
    g[tot].to=v;
    g[tot].w=w;
    g[tot].nt=head[u];
    head[u]=tot++;
}
 
bool vis[N];
int dis[N];
void spfa(int s,int n)
{
    queue<int>q;
    for(int i=0;i<=n;i++)dis[i]=0;
    q.push(s);
    vis[s]=true;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        vis[u]=false;
        for(int i=head[u];i!=-1;i=g[i].nt)
        {
            int to=g[i].to;
            if(dis[to]<dis[u]+g[i].w)
            {
                dis[to]=dis[u]+g[i].w;
                if(!vis[to])
                {
                    q.push(to);
                    vis[to]=true;
                }
            }
        }
    }
    dis[s]=0;
    for(int i=1;i<=n;i++)
    {
        cout<<dis[i]-dis[i-1]<<" ";
    }
    cout<<endl;
}
 
int main()
{
    int n;
    cin>>n;
    s[0]=0;
    memset(head,-1,sizeof(head));
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    adeEdge(0,2,(a[1]*2));
    adeEdge(2,0,-(a[1]*2+1));
    adeEdge(n-2,n,(a[n]*2));
    adeEdge(n,n-2,-(a[n]*2+1));
    for(int i=2;i<=n-1;i++)
    {
        adeEdge(i-2,i+1,(a[i]*3));
        adeEdge(i+1,i-2,-(a[i]*3+2));
    }
    for(int i=1;i<=n;i++)adeEdge(i-1,i,1);
    spfa(0,n);
    return 0;
}

差分约束总结自http://www.cppblog.com/menjitianya/archive/2015/11/19/212292.html

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值