差分约束系统例题详解.docx
《差分约束系统例题详解.docx》由会员分享,可在线阅读,更多相关《差分约束系统例题详解.docx(24页珍藏版)》请在冰豆网上搜索。
差分约束系统例题详解
差分约束系统例题详解
例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;jRelax(edge[j].s,edge[j].e,edge[j].v);
}
}
for(j=0;jif(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