摘自馮威論文——《數與圖的完美結合》
設num[ i ]為i時刻能夠開始工作的人數,x[ i ]為實際僱傭的人數,那麼x[ I ]<=num[ I ]。
設r[ i ]為i時刻至少需要工作的人數,於是有如下關係:
x[ I-7 ]+x[ I-6 ]+x[ I-5 ]+x[ I-4
]+x[ I-3 ]+x[ I-2 ]+x[ I-1 ]+x[ I ]>=r[ I ]
設s[ I ]=x[ 1 ]+x[ 2 ]…+x[ I ],得到
0<=s[ I ]-s[ I-1 ]<=num[ I ], 0<=I<=23
s[ I ]-s[ I-8 ]>=r[ I ], 8<=I<=23
s[ 23 ]+s[ I ]-s[ I+16 ]>=r[ I ], 0<=I<=7
對於以上的幾組不等式,我們採用一種非常笨拙的辦法處理這一系列的不等式(其實也是讓零亂的式子變得更加整齊,易於處理)。首先我們要明白差分約束系統的應用對象(它通常針對多個二項相減的不等式的)於是我們將上面的所有式子都轉化成兩項未知項在左邊,另外的常數項在右邊,且中間用>=串連的式子,即:
s[ I ]-s[ I-1 ]>=0
(0<=I<=23)
s[ I-1 ]-s[ I ]>=-num[ I ]
(0<=I<=23)
s[ I ]-s[ I-8 ]>=r[ I ]
(8<=I<=23)
s[ I ]-s[ I+16 ]>=r[ I ]-s[ 23 ]
(0<=I<= 7)
論文有點小小的漏洞,也導致了它論文附帶的程式是錯誤的,有BUG
可行方案中還差一個約束條件
S[23] - S[-1] >= sum;sum為僱傭的出納員總數
將所有形如 A-B>=C 的式子 我們從節點B 引出一條有向邊指向A邊的權值為C (這裡注意由於左右確定,式子又是統一的>=的不等式,所以A和B是相對確定的,邊是一定是指向A的) ,圖就建成了 。
在程式中,我用0作為上面說的-1來構圖
//差分約束系統(SPFA)<br />//對於差分約束系統,建立的不等式是>=則在SPFA中求最長路<br />//反之求最短路<br />//注意兩者對DIS初始化的不同<br />#include<iostream><br />#include<queue><br />#define INF 1000000000<br />using namespace std;<br />int r[25],t[25],caseN,n,T;<br />int head[25],V[700],next[700],W[700];<br />int cnt[25],dis[25];<br />bool inq[25];<br />int m;<br />inline void addEdge(int u,int v,int w)//新學的數組存放鄰接表的方式<br />{<br />V[m] = v;<br />W[m] = w;<br />next[m] = head[u];//head為表頭,調試後就能理解,這是倒著插的過程<br />head[u] = m++;<br />}<br />int SPFA(int ans)<br />{<br />memset(inq,0,sizeof(inq));//元素是否在隊列中<br />memset(cnt,0,sizeof(cnt));//記錄結點入隊列的次數<br />for(int i = 1;i <= 24;++i)dis[i] = -INF;<br />queue<int> q;<br />q.push(0);<br />inq[0] = 1;<br />dis[0] = 0;<br />cnt[0]++;<br />while(!q.empty())//SPFA+鄰接表模板<br />{<br />int u = q.front();q.pop();<br />inq[u] = 0;<br />for(int e = head[u];e != -1;e = next[e])<br />{<br />if(dis[u] + W[e] > dis[V[e]])<br />{<br />dis[V[e]] = dis[u] + W[e];<br />if(!inq[V[e]])<br />{<br />q.push(V[e]);<br />inq[V[e]] = 1;<br />if(++cnt[V[e]] > 24)return -1;//結點入度次數,如果超過|V|則說明存在迴路<br />}<br />}<br />}<br />}<br />if(dis[24] == ans)return 1;//如果不存在正權迴路且S[24]恰好等於ANS,則說明找到可行解,切ANS為最優解<br />else return 0;<br />}<br />void buildGraph(int ans)<br />{<br />m = 0;//重建立圖時,邊數記得回零<br />memset(head,-1,sizeof(head));<br />addEdge(0,24,ans);<br />for(int i = 1;i <= 24;++i)<br />{<br />addEdge(i - 1,i,0);<br />addEdge(i,i - 1,-t[i]);<br />}<br />for(int i = 1;i <= 8;++i)addEdge(i + 16,i,r[i] - ans);<br />for(int i = 9;i <= 24;++i)addEdge(i - 8,i,r[i]);<br />}<br />int main()<br />{<br />//freopen("in.txt","r",stdin);<br />scanf("%d",&caseN);<br />bool ok;<br />while(caseN--)<br />{<br />memset(t,0,sizeof(t));<br />ok = 0;<br />for(int i = 1;i <= 24;++i)scanf("%d",&r[i]);<br />scanf("%d",&n);<br />for(int i = 0;i < n;++i)<br />{<br />scanf("%d",&T);<br />t[T+1]++;<br />}<br />for(int ans = 0;ans <= n;++ans)<br />{<br />buildGraph(ans);<br />if(SPFA(ans) > 0)<br />{<br />printf("%d/n",ans);<br />ok = 1;<br />break;<br />}<br />}<br />if(!ok)printf("No Solution/n");<br />}<br />return 0;<br />}