嗯,這兩天用空閒時間把差分約束系統學習了一下。話說暑假已經快結束了,在忙碌的時候,依然悠哉悠哉地學算法真的好嗎。。。算了,學都已經學了,就把它放到網上作紀念吧(纔不是強迫症呢,哼!)。因爲是剛學的,所以有很多地方可能理解不當,希望大神們多加指教。
差分約束系統,就是將不等式的約束問題,抽象成最短路問題的一種方法,屬於線性規劃的一種。所以在此之前至少要熟練掌握Bellman-ford算法或者spfa算法的其中一種。Dijkstra是不行的,因爲要處理負權邊的問題。
首先我們先看看如何把不等式的約束問題轉化爲圖論的最短路問題。假設有這樣的約束條件:S[3]-S[0]<=k1, S[5]-S[3]<=k2,那麼兩式相加,則可以得出S[5]-S[0]<=k1+k2。可以抽象成,從節點0到3的距離爲k1,從節點3到節點5的距離爲k2.因爲要滿足所有的約束條件,對於<=這個關係,我們只需要求出0到5的最短路,最短路的長度,就是S[0]-S[5]的最大值。如果是>=這個關係,則需要求出最長路,最長路的長度,就是S[5]-S[0]的最小值。對於任意的S[i]-S[j]<=ki也可以根據上述條件很輕鬆地建圖。如果不等式的關係不一致怎麼辦?比如S[i]-S[j]>=k1, S[k]-S[j]<=k2.那麼可以對於其中的一個不等式,兩邊同時乘以-1,可以得到S[j]-S[k]>=-k2,然後建圖求解。
另外,不等式除了有一個解的情況,還有無數個解的情況和無解的情況,這又該如何判斷呢?這裏,在下就直接說方法了,不加證明了(拿特殊值代入法也能幫助理解)。當圖中存在負環的時候,無解。當最後一個節點沒有被更新的時候,有無窮多個解。
差分約束系統介紹完了,下面我們來看例題hdu1531.
hdu1531這一題,赤裸裸的差分約束系統的題目。就是國王給出一堆不等式,然後判斷這個不等式所滿足的數列是否存在,如果存在則輸出lamentable king,如果不存在,則輸出successful conspiracy。好的,下面來說明這道題。
因爲這道題沒有讓你輸出解,只讓你輸出是否有解,題目就變得更加簡單了。設那個數列a1+a2+a3+...+an=Sn可以獲得題目上的條件有兩種可能,①S[si+ni]-S[si-1]>ki,②S[si+ni]-S[si-1]<ki。因爲是<和>符號,我們需要把它換成>=和<=,怎麼辦呢?直接對於ki操作即可,變成>=ki+1,<=ki-1。最後變形完成可得:①S[si-1]-S[si+ni]<=-ki-1,②S[si+ni]-S[si-1]<=ki-1。然後直接建圖,從0到n過一遍spfa或者bellman-ford就可以了,如果有負環,則輸出successful conspiracy,如果沒有則輸出lamentable king。但是需要注意的是,要使整個圖聯通才可以,所以將0節點設爲虛點,我們在這裏叫他超級節點。這個節點與所有點連通,權值爲小於dist的初值的無窮大。我這裏做的是求最短路。還有一點需要注意的是,因爲是從0到n所以有n+1個節點,所以入隊次數大於n+1時,才能被判斷是負環,也算是一個小細節了,下面上代碼:
代碼君:
#include <cstdio>
#include <algorithm>
#include <string>
#include <cstring>
using namespace std;
typedef struct
{
int x,y,z;
}atp;
atp line[220];
int s[110],nn[110],kk[110],found[110],dist[110],hash[110],flag,n,m,d[5000];
char o[110][110];
bool cmp(atp a,atp b)
{
return (a.x<b.x);
}
void spfa()
{
int f,r,vis[110],i;
f=1;r=1;
memset(vis,0,sizeof(vis));
d[f]=0; dist[0]=0; vis[0]=1;
while(f<=r)
{
for(i=found[d[f]];line[i].x==d[f]&&i<=m+n;i++)
{
if(dist[line[i].y]>dist[line[i].x]+line[i].z)
{
dist[line[i].y]=dist[line[i].x]+line[i].z;
if(!vis[line[i].y])
{
if(hash[line[i].y]>n+1) //要注意是>n+1而不是n
{
flag=1;
return;
}
r++;
vis[line[i].y]=1;
d[r]=line[i].y;
hash[line[i].y]++;
}
}
}
vis[d[f]]=0;
f++;
}
}
int main()
{
int i,j,k;
while(scanf("%d",&n)!=EOF)
{
if(n==0) break;
scanf("%d",&m);
for(i=1;i<=m;i++)
scanf("%d%d%s%d",&s[i],&nn[i],o[i],&kk[i]);
k=0;
for(i=m+1;i<=m+n+1;i++)
{
line[i].x=0;
k++;
line[i].y=k;
line[i].z=10000000;
}
for(i=1;i<=m;i++)
{
if(strcmp(o[i],"gt")==0)
{
line[i].x=s[i]+nn[i];
line[i].y=s[i]-1;
line[i].z=-kk[i]-1;
}
if(strcmp(o[i],"lt")==0)
{
line[i].x=s[i]-1;
line[i].y=s[i]+nn[i];
line[i].z=kk[i]-1;
}
}
k=1;
for(i=0;i<=100;i++)
dist[i]=2147483647;
sort(line+1,line+m+n+1,cmp);
line[0].x=-1;
for(i=1;i<=m+n;i++)
{
if(line[i].x!=line[i-1].x)
found[line[i].x]=i;
}
memset(hash,0,sizeof(hash));
flag=0;
spfa();
if(flag==1)
printf("successful conspiracy\n");
else
printf("lamentable kingdom\n");
}
return 0;
}