http://acm.hdu.edu.cn/showproblem.php?pid=1531
题意:n个点m个关系,n代表一个序列:a[1]+a[2]+a[3]+...+a[n],m代表有多少个子序列(约束条件),每个约束条件的格式如下,s(对应序列中起点的序号) len(对应序列的长度) gt(大于约束值)/lt(小于约束值) w(约束值大小),前两个参数确定了子序列,子序列的和与约束值构成约束条件。求问在满足这些约束值的前提下,是否能够找出可以存在的原序列。
ps:要不是别人的题解,我连题都看不懂。
思路:老实说刚开始想不到差分约束。这题并没有对每一个元素给出确定值,只是问是否可以满足所有的约束条件。关键是问题的转化,原本格式是a[s1]+a[s2]+...a[s+len]和某值比较,这怎么搞?但是注意这些元素是连续的,所以可以变成S[s+len]-S[s-1],我们以前学过的等差数列技巧。这样加上比如小于的判断值,就构成了类似S[s+len]-S[s-1]<w的约束条件,只要所有约束条件符号相同,就可以构成一个差分约束系统。
想到这里,个人认为差分约束和最短路实质上不一样,只不过解法相同,是最短路的特例或者应用吧。好,那这题不同于poj3159,并不是求最大差距,怎么能转化成最短路呢?个人在此没有比较好的解释,只送上结论吧:
>=,求最小值,做最长路;
<=,求最大值,做最短路。
那么判断是否可以满足所有约束条件,就是这条最短路是否存在,这样就转变为了求负环,那么就是普通的spfa求负环了,判断条件就不多说了。注意这里用spfa求出的加上所有的约束条件也许并不能包含所有点,但是即使不处理剩余的点,那些剩余的点相当于没约束想取几就取几,不影响最后结果。
这里给出两种约束条件>和<,都要转化为<=才能进入系统。那么a[s1]+a[s2]+...a[s+len]>w就转变为S[s-1]-S[s+len]<=-w-1,a[s1]+a[s2]+...a[s+len]<w就转变为S[s+len]-S[s-1]<=w-1。这样在建立边的时候按照这种不等式建立。由于spfa是单源最短路,而这题没有明确的源点,所以构造超级源点。我说的也不怎么详细而且都是自己的看法,具体还是看上面大牛的博客吧= =。
#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <queue>
#include <stack>
using namespace std;
typedef long long LL;
const int N = 300;
const int INF = 0x3f3f3f3f;
int dis[N], head[N], out[N], cnt, n;
bool instack[N];
struct node
{
int v, w, next;
}edge[N];
void add(int u, int v, int w)
{
edge[cnt] = (struct node){v, w, head[u]};
head[u] = cnt++;
}
void spfa()
{
memset(instack, false, sizeof(instack));
memset(out, 0, sizeof(out));
stack<int>sta;
for(int i = 0; i <= n+1; i++)
dis[i] = INF;
dis[n+1] = 0;
instack[n+1] = true;
sta.push(n+1);
out[n+1]++;
while(!sta.empty())
{
int u = sta.top();
sta.pop();
instack[u] = false;//记得还原!!
out[u]++;
if(out[u] > n+1)
{
printf("successful conspiracy\n");
return;
}
for(int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].v;
if(dis[u]+edge[i].w<dis[v])
{
dis[v] = dis[u]+edge[i].w;
if(!instack[v])
{
instack[v] = true;
sta.push(v);
}
}
}
}
printf("lamentable kingdom\n");
}
int main()
{
// freopen("in.txt", "r", stdin);
int m, s, len, w;
char str[5];
while(~scanf("%d%d", &n, &m))
{
if(n == 0) break;
cnt = 0;
memset(head, -1, sizeof(head));
for(int i = 1; i <= m; i++)
{
scanf("%d%d%s%d", &s, &len, str, &w);
if(str[0] == 'g') add(s+len, s-1, -w-1);
else if(str[0] == 'l') add(s-1, s+len, w-1);
}
for(int i = 0; i <= n; i++)//前面计算序列和的差时,由于得到中间的子序列值被减的序列和必须减一(s-1),所以这里必须把0也一同初始化。
add(n+1, i, 0);
spfa();
}
return 0;
}