pku1364 差分约束系统

King

已知一个序列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写的:

ContractedBlock.gif ExpandedBlockStart.gif 代码
 
   
#include < stdio.h >
#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|次的时候,即可判断有负权回路。

 

ContractedBlock.gif ExpandedBlockStart.gif 代码
 
   
#include < stdio.h >
#include
< string .h >
#define INF 0xfffffff
#define NN 106
int index, n;
int dis[NN]; /* 保存到源点距离,在这里没有设源,没有含义 */
int root[NN]; /* root[i] 找到与i节点相连的第一条后继边 */
int mark[NN]; /* 标记是否已入队 */
int next[NN]; /* 邻接的下一个定点 */
int cnt[NN]; /* 保存节点入队次数 */
struct node{
int e, v;
}edge[NN];

void add( int a, int b, int c){
int head, tmp;
edge[index].e
= b;
edge[index].v
= c;
head
= root[a];
// key1
if (head == - 1 ){
root[a]
= index;
}
else {
tmp
= head;
while (next[tmp] != - 1 ){
tmp
= next[tmp];
}
next[tmp]
= index;
}
next[index]
= - 1 ;
index
++ ;
}

void Init(){
int i;
for (i = 0 ; i <= n; i ++ ){
dis[i]
= INF;
}
}

/* 查找负边权回路,1表示存在 */
int Spfa(){
int i, len, cur, tmp, head, nxt;
int que[NN];
Init();
len
= 0 ;
// key2
for (i = 0 ; i <= n; i ++ ){
if (root[i] != - 1 ){
que[len
++ ] = i;
cnt[i]
= 1 ;
mark[i]
= 1 ;
}
}
// key3
for (i = 0 ; i != len; i = (i + 1 ) % (n + 2 )){
cur
= que[i];
head
= root[cur];
tmp
= head;
while (tmp != - 1 ){
nxt
= edge[tmp].e;
if (dis[nxt] > dis[cur] + edge[tmp].v){
dis[nxt]
= dis[cur] + edge[tmp].v;

if ( ! mark[nxt]){
mark[nxt]
= 1 ;
cnt[nxt]
++ ;
que[len]
= nxt;
len
= (len + 1 ) % (n + 2 );
// key4
if (cnt[nxt] > n + 1 ) return 1 ;
}
}
tmp
= next[tmp];
}
mark[cur]
= 0 ;
}
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 ;
memset(root,
- 1 , sizeof (root));
while (m -- ){
scanf(
" %d%d%s%d " , & a, & b, str, & c);
if (str[ 0 ] == ' l ' ){
add(a - 1, a
+ b, c - 1 );
}
else {
add(a + b, a
- 1 , - c - 1 );
}
}
memset(cnt,
0 , sizeof (cnt));
memset(mark,
0 , sizeof (mark));

if (Spfa()) puts( " successful conspiracy " );
else puts( " lamentable kingdom " );
}
return 0 ;
}

 

转载于:https://www.cnblogs.com/ylfdrib/archive/2010/07/20/1781158.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值