1. 程式人生 > >2014.11.22 差分約束學習筆記

2014.11.22 差分約束學習筆記

同機房的fye,rivendile大神早就學了很久很久差分約束。。。本蒟蒻卻頹了兩晚noip2014。。。(orz fye神SD rank 8!!!)於是我覺得不能等了,於是。。。於是。。。

寫了一天差分約束啊!!!11道題啊,手都疼了、、、

言歸正傳,先給出差分約束的定義

如果一個系統由n個變數和m個約束條件組成,其中每個約束條件形如xj-xi<=bk(i,j∈[1,n],k∈[1,m]),則稱其為差分約束系統(system of difference constraints)。亦即,差分約束系統是求解關於一組變數的特殊不等式組的方法。求解差分約束系統,可以轉化成圖論的單源最短路徑(或最長路徑)問題。 (摘自百度百科)

其實蠻好懂的。舉個例子,若給出a-b<=k1,b-c<=k2;a-c<=k3;那麼a-c的最大值是多少,很明顯是把三元組轉化為求c->a的最短路。反之亦反.

那麼,我們可以將給出xj-xi<=k1(1<=i,j<=n),求xn-x1的最大值,轉化為求xn-x1的最短路

                   可以將給出xj-xi>=k1(1<=i,j<=n),求xn-x1的最小值,轉化為求xn-x1的最長路.

特別地,當給出 xj-xi<k1可以轉成xj-xi<=ki-1,將xj-xi>k1可以轉成xj-xi>=k1+1,在類似地求出最長(短)路 。

最長(短)路可以用spfa求出,但貌似也可用bellman-ford。。。開始做題!!

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------           codevs 1768 種樹2&&3 裸的差分約束系統,但有隱藏條件0<=s[i]-s[i-1]<=a[i]或1 然後將<=a[i]轉為>=-a[i]   code:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int point[500001],next[2000001];
struct hp{
	int v,w;
}e[2000001];
int queue[500001],n,m,head,tail,dis[500001],a[500001];
bool exist[500001];
int main()
{
	int j,c,num,i,x,y,w,now;
	scanf("%d%d",&n,&m);
    for (i=1;i<=n;++i)
	  scanf("%d",&a[i]); 
	for (i=1;i<=m;++i)
	  {
	    scanf("%d%d%d",&x,&y,&w);
	    next[i]=point[x-1]; point[x-1]=i;
	    e[i].v=y; e[i].w=w; 
	  }
	num=m;
	for (i=0;i<=n-1;++i)
	  {
	    num++; next[num]=point[i]; point[i]=num; e[num].v=i+1; e[num].w=0;
	    num++; next[num]=point[i+1]; point[i+1]=num; e[num].v=i; e[num].w=-a[i+1];  
	  }
	memset(dis,128,sizeof(dis));
	memset(exist,false,sizeof(exist));
	memset(queue,0,sizeof(queue)); head=0;tail=1; queue[tail]=0; exist[0]=true; dis[0]=0; 
	while (head!=tail)
	  {
	    head=head%30000+1; now=queue[head]; exist[now]=false;
	    j=point[now];
	    while (j!=0)
	      {
	        c=e[j].v;
			if (dis[c]<dis[now]+e[j].w)
			  {
			    dis[c]=dis[now]+e[j].w;
			    if (!exist[c])
			      {
			        tail=tail%30000+1;
			        queue[tail]=c; 
			        exist[c]=true;
			      }
			  }
			j=next[j];    
	      }
	  }
	cout<<dis[n]<<endl;
}

一道裸的poj差分約束  3159  Candies,題意不再贅述,code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct hp{
	int v,w;
}a[150001];
int point[30001],n,m,next[150001],s[30001];
long long dis[30001];
bool exist[30001];
using namespace std;
int main()
{
	int x,y,z,c,i,top,j,now;
	scanf("%d%d",&n,&m);
	for (i=1;i<=m;++i)
	  {
	    scanf("%d%d%d",&x,&y,&z);
	    next[i]=point[x]; point[x]=i;
		a[i].v=y; a[i].w=z; 
	  }
	memset(exist,false,sizeof(exist));
	memset(dis,127,sizeof(dis));
	memset(s,0,sizeof(s)); top=1; s[1]=1; exist[1]=true; dis[1]=0;
	while (top>0)
	  {
	    now=s[top]; top--; j=point[now]; exist[now]=false;
	    while (j!=0)
	      {
	        c=a[j].v;  
	        if (dis[c]>dis[now]+a[j].w)
	          {
	            dis[c]=dis[now]+a[j].w;
	            if (!exist[c])
	              {
	                top++;
	                s[top]=c;
	                exist[c]=true;
	              }
	          }
	        j=next[j];
	      }
	  }
	printf("%lld\n",dis[n]);
} 
兩道類似於種樹2、3的poj1716&&1201 intervals&&integer intervals 注意隱含條件與轉化(0<=s[i]-s[i-1]<=1)code(intervals):
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int point[500001],next[2000001];
struct hp{
	int v,w;
}e[2000001];
int queue[500001],n,m,head,tail,dis[500001],a[500001];
bool exist[500001];
int main()
{
	int j,c,num,i,x,y,w,now,maxn,minn;
	scanf("%d",&m); minn=2100000000; maxn=0;
	for (i=1;i<=m;++i)
	  {
	    scanf("%d%d%d",&x,&y,&w);
	    maxn=max(maxn,y);
	    minn=min(minn,x);
	    next[i]=point[x-1]; point[x-1]=i;
	    e[i].v=y; e[i].w=w; 
	  }
	num=m;
	for (i=minn-1;i<=maxn-1;++i)
	  {
	    num++; next[num]=point[i]; point[i]=num; e[num].v=i+1; e[num].w=0;
	    num++; next[num]=point[i+1]; point[i+1]=num; e[num].v=i; e[num].w=-1;  
	  }
	memset(dis,128,sizeof(dis));
	memset(exist,false,sizeof(exist));
	memset(queue,0,sizeof(queue)); head=0;tail=1; queue[tail]=minn-1; exist[minn-1]=true; dis[minn-1]=0; 
	while (head!=tail)
	  {
	    head=head%30000+1; now=queue[head]; exist[now]=false;
	    j=point[now];
	    while (j!=0)
	      {
	        c=e[j].v;
			if (dis[c]<dis[now]+e[j].w)
			  {
			    dis[c]=dis[now]+e[j].w;
			    if (!exist[c])
			      {
			        tail=tail%30000+1;
			        queue[tail]=c; 
			        exist[c]=true;
			      }
			  }
			j=next[j];    
	      }
	  }
	cout<<dis[maxn]<<endl;
}
poj 3169 Layout 其實很水,只不過出現了判斷環,儲存一個點進隊次數 當其>n時,出現了正(負)環,注意隱含條件 code:
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
struct hp{
	int v,w;
}a[100000]; 
int queue[1001],num[1001],dis[1001],head,tail;
int point[1001],next[100001],maxn;
bool exist[1001];
int main()
{
	bool ff=false;
	int numx,n,mi,mj,i,j,now,c,x,y,z;
	freopen("layout.in","r",stdin);
	freopen("layout.out","w",stdout);
	scanf("%d%d%d",&n,&mi,&mj);
	numx=0;
	for (i=1;i<=mi;++i)
	  {
	  	numx++;
	    scanf("%d%d%d",&x,&y,&z);
		next[numx]=point[x]; point[x]=numx;
		a[numx].v=y; a[numx].w=z;  
	  }
	for (i=1;i<=mj;++i)
	  {
	    numx++;
	    scanf("%d%d%d",&x,&y,&z);
	    next[numx]=point[y]; point[y]=numx;
	    a[numx].v=x; a[numx].w=-z;
	  }
	for (i=2;i<=n;++i)
	  {
	    numx++;
	    next[numx]=point[i]; point[i]=numx;
	    a[numx].v=i-1; a[numx].w=0;
	  }
	memset(queue,0,sizeof(queue)); memset(exist,false,sizeof(exist));
	memset(dis,127,sizeof(dis));  maxn=dis[0]; memset(num,0,sizeof(num));
	head=0;tail=1; queue[tail]=1; exist[1]=true; num[1]++; dis[1]=0;
	while (head!=tail)
	  {
	    head=head%1000+1; now=queue[head]; exist[now]=false;
	    j=point[now]; 
		while (j!=0)
		  {
		    c=a[j].v;
		    if (dis[c]>dis[now]+a[j].w)
		      {
		        dis[c]=dis[now]+a[j].w;
		        if (!exist[c])
		          {
		            tail=tail%1000+1;
		            queue[tail]=c;
		            num[c]++; exist[c]=true;
		            if (num[c]>n)
		              ff=true;
		          }
		      }
            if (ff) break;
		    j=next[j];
		  }  
	  }
	if (ff) cout<<-1<<endl;
	else
	  {
	    if (dis[n]>=maxn) cout<<-2<<endl;
	    else cout<<dis[n]<<endl;
	  }
	fclose(stdin); fclose(stdout);
}

poj 1364 king,很水的一道差分約束,類似字首和的處理,但有可能出現圖不連通的情況,所以要作連通塊次數遍spfa,如果出現環,則輸出successful conspiracy,正常返回輸出lamentable kingdom   code:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct hp{
	int v,w;
}a[10001];
int queue[10001],head,tail,num[10001],dis[10001];
int point[10001],next[100001],n,m;
bool exist[10001],f[10001];
int main()
{
	char kind[2]; bool ff;
	int i,j,c,now,x,t,k;
	scanf("%d",&n);
	while (n!=0)
	  {
	  	memset(a,0,sizeof(a)); memset(point,0,sizeof(point)); memset(next,0,sizeof(next));
	    scanf("%d",&m);
	    for (i=1;i<=m;++i)
	      {
	        cin>>x>>t>>kind>>k;
	        if (kind[0]=='g')
	          {
	            next[i]=point[x-1];
	            point[x-1]=i;
	            a[i].v=x+t; a[i].w=k+1;
	          }
	        if (kind[0]=='l')
	          {
	            next[i]=point[x+t];
	            point[x+t]=i;
	            a[i].v=x-1; a[i].w=1-k;
	          }
		  }
		memset(f,false,sizeof(f));  ff=false;
		for (i=1;i<=n;++i)
		  if (!f[i])
		    {
		      memset(queue,0,sizeof(queue)); memset(num,0,sizeof(num));
		      memset(dis,128,sizeof(dis)); memset(exist,false,sizeof(exist)); 
		      head=0; tail=1; dis[i]=0; queue[tail]=i; num[i]++; exist[i]=true; f[i]=true;
		      while (head!=tail)
		        {
		          head=head%10000+1; now=queue[head]; exist[now]=false; 
		          j=point[now];
				  while (j!=0)
				    {
				      c=a[j].v;
				      if (dis[c]<dis[now]+a[j].w)
				        {
				          dis[c]=dis[now]+a[j].w;
				          f[c]=true;
				          if (!exist[c])
				            {
				              exist[c]=true; num[c]++;
				              tail=tail%10000+1; queue[tail]=c;
				              if (num[c]>n)
				                {
				                  ff=true; 
				                  break;
				                }
				            }
				        }
				      if (ff) break;
					  j=next[j]; 
				    }
				   if (ff) break;
		        }
		      if (ff) break;
		    }
		if (ff) printf("successful conspiracy\n");
		else printf("lamentable kingdom\n");
		scanf("%d",&n);
	  }
}
poj 2983 Is the Information Reliable 

一道比較有難度的差分約束,首先,由於題目給出一種情況的是確切的距離,我們可以轉化為k<=s[i]-s[j]<=k,進一步變號為{s[j]-s[i]>=-k,s[i]-s[j]>=k},另外一種只說明瞭某一點與另外一點有位置關係,對此可以轉化為s[i]-s[j]>=0,最後跑一遍最長路即可 code:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
struct hp{
	int v,w;
}a[200001];
int queue[1001],head,tail,num[1001],dis[1001];
int point[1001],next[200001],n,m;
bool exist[1001],f[1001];
using namespace std;
int main()
{
	int i,c,numx,now,j,ai,b; bool ff; char kind;
	while (scanf("%d%d",&n,&m)==2)
	  {
	  	numx=0; memset(point,0,sizeof(point)); memset(next,0,sizeof(next)); memset(a,0,sizeof(a));
	    for (i=1;i<=m;++i)
	      {
	        cin>>kind;
			if (kind=='P')
			  { 
			    scanf("%d%d%d",&ai,&b,&c);
	            numx++; next[numx]=point[b]; point[b]=numx; a[numx].v=ai; a[numx].w=c;
	            numx++; next[numx]=point[ai]; point[ai]=numx; a[numx].v=b; a[numx].w=-c;
	          }
	        if (kind=='V')
	          {
	            scanf("%d%d",&ai,&b);
	            numx++; next[numx]=point[b]; point[b]=numx; a[numx].v=ai; a[numx].w=1; 
	          }
	      }
	    memset(f,false,sizeof(f)); ff=false;
	    for (i=1;i<=n;++i)
	      if (f[i]==false)
	        {
	          memset(num,0,sizeof(num)); memset(queue,0,sizeof(queue));
	          memset(exist,false,sizeof(exist)); memset(dis,128,sizeof(dis)); 
	          head=0; tail=1; dis[i]=0; exist[i]=true; queue[tail]=i; num[i]++; f[i]=true;
	          while (head!=tail)
	            {
	              head=head%1000+1; now=queue[head]; exist[now]=false;
				  j=point[now];
				  while (j!=0)
				    {
				      c=a[j].v;
				      if (dis[c]<dis[now]+a[j].w)
				        {
				          dis[c]=dis[now]+a[j].w;
						  f[c]=true;
						  if (!exist[c]) 
						    {
						      exist[c]=true;
						      tail=tail%1000+1;
						      queue[tail]=c;
						      num[c]++;
						      if (num[c]>n)
						        {
						          ff=true; break;
						        }
						    } 
				        }
				      if  (ff) break;
				      j=next[j];
				    } 
				   if  (ff) break; 
	            }
	          if (ff) break;
	        }
	    if (ff) printf("Unreliable\n");
	    else printf("Reliable\n");
	  }
}
COGS 月考統計  一道比較水的題,但題目要求求出最小值,卻給出了s[i]-s[j]<=k的條件,因此需轉化為s[j]-s[i]>=-k,另外圖也可能不連通,可以加設超級源點0,使其與所有點連通,作為最底成績0,跑一遍最長路即可。  code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct hp{
	int v,w;
}a[50001];
int queue[10000],head,tail,dis[10000];
int point[10000],next[50001],n,m,num[10000];
bool exist[10000];
int main()
{
	int i,x,y,z,j,c,now; bool ff=false;
	freopen("ExamStat.in","r",stdin); freopen("ExamStat.out","w",stdout); 
	scanf("%d%d",&n,&m);
	for (i=1;i<=m;++i)
	  {
	    scanf("%d%d%d",&x,&y,&z);
	    next[i]=point[x]; point[x]=i;
	    a[i].v=y; a[i].w=-z;
	  }
	for (i=1;i<=n;++i)
	  {
	    next[m+i]=point[0]; point[0]=m+i;
		a[m+i].v=i; a[m+i].w=0; 
	  }
	memset(num,0,sizeof(num)); memset(dis,128,sizeof(dis));
	memset(queue,0,sizeof(queue)); memset(exist,false,sizeof(exist));
	head=0; tail=1; queue[tail]=0; num[0]++; dis[0]=0; exist[0]=true;
	while (head!=tail)
	  {
	    head=head%10000+1; now=queue[head]; exist[now]=false;
	    j=point[now];
	    while (j!=0)
	      {
	        c=a[j].v; 
	        if (dis[c]<dis[now]+a[j].w)
	          {
	            dis[c]=dis[now]+a[j].w;
	            if (!exist[c])
	              {
	                num[c]++; exist[c]=true;
	                tail=tail%10000+1;
	                queue[tail]=c;
	                if (num[c]>n)
	                  {
	                    ff=true;
	                    break;
	                  }
	              }
	          }
	        j=next[j];
	        if (ff) break;
	      }
	    if (ff) break;
	  }
	if (ff) printf("SOMEONE LAY!");
	else 
	  {
	    for (i=1;i<=n;++i)
	      printf("%d ",dis[i]);
		printf("\n"); 
	  } 
	fclose(stdin); fclose(stdout);
}

以下兩題需先迴圈起點,根據長度推出終點

COGS 01串
題目較之前發生了變化,給出兩個不相干的條件,a0<=s[i][0]-s[j][0]<=b0,a1<=s[i][1]-s[j][1]<=b1,但仔細觀察可知,區間長度一定,可以將其全部轉化為0的情況i-j+1-b1<=s[i][0]-s[j][0]<=i-j+1-a1,再就是隱含條件0<=s[i][0]-s[j][0]<=1,再都轉化為<=,跑一遍最短路,輸出每個點的1-(dis[i]-dis[i-1](零的個數))。。。code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct hp{
	int v,w;
}a[10000];
int queue[1001],head,tail,num[1001],dis[1001];
int point[1001],next[10001],n,a0,b0,l0,a1,b1,l1;
bool exist[1001];
int main()
{
	int i,nx=0,now,c,j; bool ff=false;
	freopen("sequence.in","r",stdin); freopen("sequence.out","w",stdout);
	scanf("%d%d%d%d%d%d%d",&n,&a0,&b0,&l0,&a1,&b1,&l1);
	for (i=1;i<=n;++i)
	  {
	    nx++; next[nx]=point[i-1]; point[i-1]=nx; a[nx].v=i; a[nx].w=1;
	    nx++; next[nx]=point[i]; point[i]=nx; a[nx].v=i-1; a[nx].w=0;
	  }
	for (i=1;i<=n-l0+1;++i)
	  {
	    nx++; next[nx]=point[i-1]; point[i-1]=nx; a[nx].v=i+l0-1; a[nx].w=b0;
		nx++; next[nx]=point[i+l0-1]; point[i+l0-1]=nx; a[nx].v=i-1; a[nx].w=-a0; 
	  }
	for (i=1;i<=n-l1+1;++i)
	  {
	    nx++; next[nx]=point[i-1]; point[i-1]=nx; a[nx].v=i+l1-1; a[nx].w=l1-a1;
		nx++; next[nx]=point[i+l1-1]; point[i+l1-1]=nx; a[nx].v=i-1; a[nx].w=b1-l1; 	
	  }
    memset(queue,0,sizeof(queue)); memset(dis,127,sizeof(dis));
	memset(exist,false,sizeof(exist)); memset(num,0,sizeof(num)); ff=false;
	head=0; tail=1; queue[tail]=0; dis[0]=0; exist[0]=true; num[0]++;
	while (head!=tail)
	  {
	    head=head%1000+1; now=queue[head]; exist[now]=false;
	    j=point[now];
	    while (j!=0)
	      {
	        c=a[j].v;
	        if (dis[c]>dis[now]+a[j].w)
	          {
	            dis[c]=dis[now]+a[j].w;
	            if (!exist[c])
	              {
	                exist[c]=true; tail=tail%1000+1;
	                queue[tail]=c; num[c]++;
	                if (num[c]>n)
	                  {
	                    ff=true;
	                    break;
	                  }
	              }
	          }
	        j=next[j];
	        if (ff) break;
	      }
	    if (ff) break;
	  }
	if (ff) printf("-1\n");
	else
	  {
	    for (i=1;i<=n;++i)
	      {
	        if (dis[i]-dis[i-1]==1)
	          printf("0");
	        else
	          printf("1");
	      }
	    printf("\n");
	  }
	fclose(stdin); fclose(stdout);
}
COGS 數列問題

基本同上一題,但給出的是s[i]-s[j]>0或s[i]-s[j]<0,所以轉化為s[j]-s[i]<=-1或s[i]-s[j]<=-1,此題依舊可能不連通,連通塊遍spfa。。。輸出dis[i]-dis[i-1];

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct hp{
	int v,w;
}a[2000];
int queue[101],head,tail,dis[101],num[101];
int point[101],next[2000],n,p,q;
bool exist[101],f[101];
int main()
{
	int i,nx=0,now,j,c; bool ff=false;
	freopen("topoa.in","r",stdin); freopen("topoa.out","w",stdout);
	scanf("%d%d%d",&n,&p,&q);
	for (i=1;i<=n-p+1;++i)
	  {nx++; next[nx]=point[i+p-1]; point[i+p-1]=nx; a[nx].v=i-1; a[nx].w=-1;}
	for (i=1;i<=n-q+1;++i)
	  {nx++; next[nx]=point[i-1]; point[i-1]=nx; a[nx].v=i+q-1; a[nx].w=-1;}
	memset(f,0,sizeof(f));
	for (i=0;i<=n;++i)
	  if (!f[i])
	    {
	    {
	      memset(queue,0,sizeof(queue)); memset(num,0,sizeof(num));
	      memset(exist,false,sizeof(exist));
	      head=0; tail=1; queue[tail]=i; if (i!=0)dis[i]=dis[i-1]; else dis[i]=0; num[i]++; exist[i]=true; f[i]=true;
	      while (head!=tail)
	        {
	          head=head%100+1; now=queue[head]; exist[now]=false;
	          j=point[now];
	          while (j!=0)
	            {
	              c=a[j].v;
	              if (dis[c]>dis[now]+a[j].w)
	                {
	                  dis[c]=dis[now]+a[j].w;
	                  f[c]=true;
	                  if (!exist[c])
	                    {
	                      exist[c]=true; num[c]++; tail=tail%100+1;
	                      queue[tail]=c;
	                      if (num[c]>n)
	                        {
	                          ff=true; break;
	                        }
	                    }
	                }
	              j=next[j];
	              if (ff) break;
	            }
	          if (ff) break;
	        }
	      if (ff) break; 
        }
	if (ff) printf("no\n");
	else
      {
        for (i=1;i<=n;++i)
          printf("%d ",dis[i]-dis[i-1]);
        printf("\n");
      }
    fclose(stdin); fclose(stdout);  
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------                                                                                                                                                                                                                                                                                          Lcomyn

                                                                                                                                                                                                                                                                                   2014.11.22&&23