差分约束系统例题详解

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

差分约束系统例题详解

例1:pku1364

已知一个序列a[1], a[2], ......, a[n],给出它的若干子序列以及对该子序列的约束条件,例如a[si], a[si+1], a[si+2], ......, a[si+ni],且a[si]+a[si+1]+a[si+2]+......+a[si+ni] < or > ki。问题关键在于如何转化约束条件,开始我想以序列中的每一个值做一个点,如a[1], a[2] ……,就发现他们的关系是多者之间的关系,跟差分系统对不上,在看到MickJack的讲解后,才知道,用前n项和来转化成两两之间的关系。

如:s[a] + s[a+1] + …… + s[b] < c 可以转化成前n项和sum[b] - sum[a - 1] < c,为了能用Bellman_Ford,即将< 转化成<= ,sum[b] - sum[a - 1] <= c - 1。

我用Bellman_Ford写的:

#include

#define INF 0xfffffff

#define NN 104

int index, n;

int dis[NN];

struct node{

int s, e, v;

}edge[NN];

void add(int a, int b, int c){

edge[index].s = a;

edge[index].e = b;

edge[index].v = c;

index++;

}

void Init(int s){

int i;

for (i = 0; i <= n; i++){

dis[i] = INF;

}

dis[s] = 0;

}

void Relax(int s, int e, int v){

if (dis[e] > dis[s] + v){

dis[e] = dis[s] + v;

}

}

/*查找负边权回路,1表示存在*/

int Bellman_Ford(){

Init(0);

int i, j;

for (i = 1; i <= n; i++){

for (j = 0; j < index; j++){

Relax(edge[j].s, edge[j].e, edge[j].v);

}

}

for (j = 0; j < index; j++){

if (dis[edge[j].e] > dis[edge[j].s] + edge[j].v){

return 1;

}

}

return 0;

}

int main()

{

char str[4];

int a, b, c, m;

while (scanf("%d", &n) != EOF){

if (n == 0) break;

scanf("%d", &m);

index = 0;

while (m--){

scanf("%d%d%s%d", &a, &b, str, &c);

if (str[0] == 'l'){

add(a - 1, a + b, c - 1); // c-1 使得< 变成<= 就能够判负环了

}else{

add(a + b, a - 1, -c - 1);

}

}

if(Bellman_Ford()) puts("successful conspiracy");

else puts("lamentable kingdom");

}

return 0;

}

做这题时,我用重新学习了下Bellman_Ford,感觉这个写的挺不错的。

为了锻炼一下,我又写了个SPFA,错了好次才过。

差分系统跟最短路还是有点区别的,就是得附加一个源点,使得他与所有点都相连,边权赋为零,这样才能保证连通,这一题我就错这个地方了。开始写Bellman_Ford的时候没注意,没有加点也过了,后来发现SPFA就过不了,想了想,还是有道理的,bellman是对边操作,不加源点,照样能对任意一条边松弛,而SPFA就不一样,是对点操作,通过点,对与之相连的边进行松弛,就必须得附加源点了,不过有种方法可以不加,SPFA时,将所有点先入队,或将邻接表的根节点入队。总结了以下关键点。

key1:做SPFA时,最好用邻接表存储,才能达到效果。

key2:为了不增加源点,将所有邻接表的根节点入队。

key3:为了减少队列队内存的负担,用循环队列实现,手动模拟更快,以前都是每向下一层,更新一次队列,现在是用循环,当队列满时更新队列,队列长度要大于队列一次可能保存的最多节点,这里就是节点的总个数,最多n+1个点同时都在队里,因为有mark 数组的限制,这里队列长度我取了n+2。

key4:当任意一节点入队次数超过|V|次的时候,即可判断有负权回路。

#include

#include

#define INF 0xfffffff

#define NN 106

int index, n;

int dis[NN]; /* 保存到源点距离,在这里没有设源,没有含义*/

int root[NN]; /* root[i] 找到与i节点相连的第一条后继边*/

相关文档
最新文档