差分约束系统例题详解.docx

上传人:b****8 文档编号:10491087 上传时间:2023-02-13 格式:DOCX 页数:24 大小:19.87KB
下载 相关 举报
差分约束系统例题详解.docx_第1页
第1页 / 共24页
差分约束系统例题详解.docx_第2页
第2页 / 共24页
差分约束系统例题详解.docx_第3页
第3页 / 共24页
差分约束系统例题详解.docx_第4页
第4页 / 共24页
差分约束系统例题详解.docx_第5页
第5页 / 共24页
点击查看更多>>
下载资源
资源描述

差分约束系统例题详解.docx

《差分约束系统例题详解.docx》由会员分享,可在线阅读,更多相关《差分约束系统例题详解.docx(24页珍藏版)》请在冰豆网上搜索。

差分约束系统例题详解.docx

差分约束系统例题详解

差分约束系统例题详解

例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]ki。

问题关键在于如何转化约束条件,开始我想以序列中的每一个值做一个点,如a[1],a[2]……,就发现他们的关系是多者之间的关系,跟差分系统对不上,在看到MickJack的讲解后,才知道,用前n项和来转化成两两之间的关系。

如:

s[a]+s[a+1]+……+s[b]

我用Bellman_Ford写的:

#include

#defineINF0xfffffff

#defineNN104

intindex,n;

intdis[NN];

structnode{

ints,e,v;

}edge[NN];

voidadd(inta,intb,intc){

edge[index].s=a;

edge[index].e=b;

edge[index].v=c;

index++;

}

voidInit(ints){

inti;

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

dis[i]=INF;

}

dis[s]=0;

}

voidRelax(ints,inte,intv){

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

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

}

}

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

intBellman_Ford(){

Init(0);

inti,j;

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

for(j=0;j

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

}

}

for(j=0;j

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

return1;

}

}

return0;

}

intmain()

{

charstr[4];

inta,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("successfulconspiracy");

elseputs("lamentablekingdom");

}

return0;

}

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

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

差分系统跟最短路还是有点区别的,就是得附加一个源点,使得他与所有点都相连,边权赋为零,这样才能保证连通,这一题我就错这个地方了。

开始写Bellman_Ford的时候没注意,没有加点也过了,后来发现SPFA就过不了,想了想,还是有道理的,bellman是对边操作,不加源点,照样能对任意一条边松弛,而SPFA就不一样,是对点操作,通过点,对与之相连的边进行松弛,就必须得附加源点了,不过有种方法可以不加,SPFA时,将所有点先入队,或将邻接表的根节点入队。

总结了以下关键点。

key1:

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

key2:

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

key3:

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

key4:

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

 

#include

#include

#defineINF0xfffffff

#defineNN106

intindex,n;

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

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

intmark[NN];/*标记是否已入队*/

intnext[NN];/*邻接的下一个定点*/

intcnt[NN];/*保存节点入队次数*/

structnode{

inte,v;

}edge[NN];

voidadd(inta,intb,intc){

inthead,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++;

}

voidInit(){

inti;

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

dis[i]=INF;

}

}

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

intSpfa(){

inti,len,cur,tmp,head,nxt;

intque[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)return1;

}

}

tmp=next[tmp];

}

mark[cur]=0;

}

return0;

}

intmain()

{

charstr[4];

inta,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("successfulconspiracy");

elseputs("lamentablekingdom");

}

return0;

}

例2:

pku3159(SPFA+栈)

小孩A认为小孩B比自己多出的最多不会超过c个糖果,也就是B-A<=c,正好符合差分约束方程,就是A到B的边权w(A,B)=c;用SPFA+栈能过。

这里有两种加边方式:

第一种:

我以前用的,用这个超时了,因为每次加边都是将边夹在邻接表的最后面,需要一个查找时间,这题数据量大,自然就超时了。

voidadd(inta,intb,intc){

inttmp;

edge[edNum].e=b;

edge[edNum].v=c;

next[edNum]=-1;

tmp=root[a];

if(tmp==-1){

root[a]=edNum;

}else{

while(next[tmp]!

=-1){

tmp=next[tmp];

}

next[tmp]=edNum;

}

edNum++;

}

第二种:

这种我刚学到的,比较好,每次把边加在最前面,突然想起sjr有一道题的加边方法和这个一样,那时怎么就看不懂,大概是不懂邻接表的缘故吧。

voidadd(inta,intb,intc){

edge[edNum].e=b;

edge[edNum].v=c;

next[edNum]=root[a];

root[a]=edNum++;

}

这题还得用栈实现,用队列超时,我开始用队列,一直TLE,我这里写了两个,Spfa()是用队列实现的,Spfa1是用栈实现的。

#include

#include

#include

#defineINF0xfffffff

#defineMM150004

#defineNN30004

intedNum,N;

structnode{

inte,v;

}edge[MM];

intnext[MM];

intdis[NN];

introot[NN];

intque[NN];

intmark[NN];

intstack[NN];

voidadd(inta,intb,intc){

inttmp;

edge[edNum].e=b;

edge[edNum].v=c;

next[edNum]=root[a];

root[a]=edNum++;

}

voidSpfa()

{

inti,quNum,tmp,nxt,cur;

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

dis[i]=INF;

}

dis[1]=0;

quNum=0;

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

if(root[i]!

=-1){

que[quNum++]=i;

mark[i]=1;

}

}

for(i=0;i!

=quNum;i=(i+1)%(N+1)){

cur=que[i];

tmp=root[cur];

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;

que[quNum]=nxt;

quNum=(quNum+1)%(N+1);

}

}

tmp=next[tmp];

}

mark[cur]=0;

}

}

voidSpfa1()

{

inti,top,tmp,nxt,cur;

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

dis[i]=INF;

}

dis[1]=0;

top=0;

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

if(root[i]!

=-1){

stack[++top]=i;

mark[i]=1;

}

}

while(top){

cur=stack[top--];

tmp=root[cur];

mark[cur]=0;

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;

stack[++top]=nxt;

}

}

tmp=next[tmp];

}

}

}

intmain()

{

intM,a,b,c,i;

scanf("%d%d",&N,&M);

edNum=0;

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

root[i]=-1;

mark[i]=0;

}

while(M--){

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

add(a,b,c);

}

Spfa1();

printf("%d\n",dis[N]);

//system("pause");

return0;

}

例3:

pku2983(SPFA+栈)

分析题意:

输入有两种形式:

1:

PABX 即B+X=A转化一下:

B-A<=-X, A-B<=X 构造边和权:

(A,B,-X),(B,A,X)

2:

VAB   即 B+1<=A转化一下:

B-A<=-1                     构造边和权:

(A,B,-1) 

WA了无数次,终于过了,不是SPFA的原因,是自己没写好啊!

每次写Bellman_Ford的时候挺简单,写SPFA的时候就死活不过,一度让我怀疑,SPFA是不是不稳定!

不过还都是因为自己不过小心,处理的时候很容易出错。

不过SPFA还是很快的,422ms,Bellman_Ford就跑了2907ms,跟没过也差不多了!

#include

#include

#defineINF0xfffffff

#defineNN1004

#defineMM200004

intedNum,N;

intnext[MM];

introot[NN];

intmark[NN];

intcnt[NN];

intdis[NN];

intstack[NN];

structnode{

inte,v;

}edge[MM];

voidadd(inta,intb,intc){

edge[edNum].e=b;

edge[edNum].v=c;

next[edNum]=root[a];

root[a]=edNum++;

}

intSpfa()

{

inti,top,cur,tmp,nxt;

top=0;

  //初始化的时候没有加源点,直接将所有有根的点入栈,也很不错

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

dis[i]=INF;

//key1

if(root[i]!

=-1){

stack[++top]=i;

cnt[i]++;

mark[i]=1;

}

}

while(top){

cur=stack[top--];

tmp=root[cur];

mark[cur]=0;

while(tmp!

=-1){

nxt=edge[tmp].e;

//key2

if(dis[nxt]>dis[cur]+edge[tmp].v){

dis[nxt]=dis[cur]+edge[tmp].v;

if(!

mark[nxt]){

cnt[nxt]++;

if(cnt[nxt]>N+1){

return1;

}

mark[nxt]=1;

stack[++top]=nxt;

}

}

tmp=next[tmp];

}

}

return0;

}

intmain()

{

inti,a,b,c,M;

charstr[2];

while(scanf("%d%d",&N,&M)!

=EOF)

{

edNum=0;

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

root[i]=-1;

mark[i]=0;

cnt[i]=0;

}

while(M--){

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

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

scanf("%d",&c);

add(b,a,c);

add(a,b,-c);

}else{

add(a,b,-1);

}

}

if(Spfa()){

puts("Unreliable");

}else{

puts("Reliable");

}

}

return0;

}

例4:

pku3169

这题还是用的SPFA+栈过的,400多ms,写得多了,发现都可以套用模块了,除了输入不太一样外,其他的都基本一样。

模块一:

负责各个数组的初始化

模块二:

负责加边,对边用邻接表处理

模块三:

SPFA用栈来实现的过程

#include

#include

#defineINF0xfffffff

#defineNN1004

#defineMM20004

intedNum,N;

intnext[MM];

introot[NN];

intmark[NN];

intcnt[NN];

intdis[NN];

intstack[NN];

structnode{

inte,v;

}edge[MM];

/*在这里我用了一个函数来完成初始化,也可以看着一个固定模块了*/

/*模块一*/

voidInit(){

inti;

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

root[i]=-1;

mark[i]=0;

cnt[i]=0;

}

}

/*模块二*/

voidadd(inta,intb,intc){

edge[edNum].e=b;

edge[edNum].v=c;

next[edNum]=root[a];

root[a]=edNum++;

}

/*函数返回-1,表示有负边权回路,就表示不可能

函数返回-2,就表示起点到终点的距离不定,这里用dis[N]==INF,来判断,因为开始就是赋值的是INF

否者,返回dis[N],这里面存的就是起点到终点的最大距离

*/

/*模块三*/

intSpfa()

{

inti,top,cur,tmp,nxt;

top=0;

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

dis[i]=INF;

if(root[i]!

=-1){

stack[++top]=i;

cnt[i]++;

mark[i]=1;

}

}

dis[1]=0;

while(top){

cur=stack[top--];

tmp=root[cur];

mark[cur]=0;

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]){

cnt[nxt]++;

if(cnt[nxt]>N+1){

return-1;

}

mark[nxt]=1;

stack[++top]=nxt;

}

}

tmp=next[tmp];

}

}

if(dis[N]==INF){

return-2;

}

returndis[N];

}

intmain()

{

intML,MD,a,b,c;

scanf("%d%d%d",&N,&ML,&MD);

Init();

while(ML--){

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

add(a,b,c);

}

while(MD--){

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

add(b,a,-c);

}

printf("%d\n",Spfa());

//system("pause");

return0;

}

例5:

pku1201

这题依然SPFA+栈过!

不同的是,这题有隐含条件,而且所求也大不一样。

由于这是对区间操作,求至少包含区间[ai,bi]中ci个点的最小集合。

可以用集合元素个数来定义变量,即num[bi]-num[ai]>=ci,但有个隐含条件就是,每个元素都是整点,取得话,最多为一,最少为0,即num[i+1]-num[i]<=1,num[i+1]-num[i

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 管理学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1