试题编号: | 201809-4 |
试题名称: | 再卖菜 |
时间限制: | 1.0s |
内存限制: | 256.0MB |
问题描述: | 问题描述 在一条街上有n个卖菜的商店,按1至n的顺序排成一排,这些商店都卖一种蔬菜。 输入格式 输入的第一行包含一个整数n,表示商店的数量。 输出格式 输出一行,包含n个正整数,依次表示每个商店第一天的菜价。 样例输入 8 样例输出 2 2 2 1 6 5 16 10 数据规模和约定 对于30%的评测用例,2<=n<=5,第二天每个商店的菜价为不超过10的正整数; |
对于这个问题,我们可以建立下面几个不等式:
假设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