p1227 關係運算圖
差分約束系統
一.定義
差分約束系統(system of difference constraints),是求解關於一組變數的特殊不等式組之方法。如果一個系統由n個變數和m個約束條件組成,形成m個形如ai-aj≤k的不等式(i,j∈[1,n],k為常數),則稱其為差分約束系統(system of difference constraints)。亦即,差分約束系統是求解關於一組變數的特殊不等式組的方法。
二.詳解
鑑於我是初學差分約束系統,對於概念其實並不是理解得很透徹, 不過等我理解透了,一定要寫一篇解釋差分約束系統的隨筆
在此奉上幾位大佬的部落格
作者:英雄哪裡出來
http://www.cppblog.com/menjitianya/archive/2015/11/19/212292.html
作者:HARD_UNDERSTAND
https://blog.csdn.net/hjt_fathomless/article/details/52463723
三.一道例題
1.題目(p1227)
描述 Description
給出一有向圖,圖中每條邊都被標上了關係運算符‘<’,‘>’,‘=’。現在要給圖中每個頂點標上一個大於等於0,小於等於k的某個整數使所有邊上的符號得到滿足。若存在這樣的k,則求最小的k,若任何k都無法滿足則輸出NO。
例如下表中最小的k為2。
結點1>結點2
結點2>結點3
結點2>結點4
結點3=結點4
如果存在這樣的k,輸出最小的k值;否則輸出‘NO’。
輸入格式 Input Format
共二行,第一行有二個空格隔開的整數n和m。n表示G的結點個數,m表示G的邊數,其中1<=n<=1000, 0<=m<=10000。全部結點用1到n標出,圖中任何二點之間最多隻有一條邊,且不存在自環。
第二行共有3m個用空格隔開的整數,第3i-2和第3i-1(1<=i<=m)個數表示第i條邊的頂點。第3i個數表示第i條邊上的符號,其值用集合{-1,0,1}中的數表示:-1表示‘<’, 0 表示‘=’, 1表示‘>’。
輸出格式 Output Format
僅一行,如無解則輸出‘NO’;否則輸出最小的k的值。
樣例輸入 Sample Input
4 4
1 2 -1 2 3 0 2 4 -1 3 4 -1
樣例輸出 Sample Output
2
時間限制 Time Limitation
各個測試點1s
2.一些感受
前些日子,由於找死,非得學最短路的四個演算法(雖然現在可以說是學的差不多了),自我感覺良好,便想找道題練練,於是,打著寫最短路的招牌,找到了這道題。
誒?,這道題怎麼這麼不對勁啊,想了半天,想了一個思路:既然他的路徑上是關係運算符,且這是一個有向圖,就說明他的節點是有著一個先後順序的,想到這些,我決定打暴力:即在輸入時按照關係運算符將節點編號排序,然後,將排在第一位的節點的值賦為0,然後遞推下去,輸出最後節點的值,。。。。。。。。。。。。。。。。。但是失敗了,因為我無法處理相同關係的節點。。。。。。。。。。。。。。
所以,只好上網查題解,於是看到了一個從未見過的名詞:差分約束系統!!!!!!!!!
3.題解
好吧,總之寫一點題解吧(雖然我也不太懂),
我們可以考慮如果輸入x,y,z,
若z== 1 , 那麼x到y連一條權值1的邊;
z== -1 ,y到x連一條權值1的邊;
z==0 , 則兩個點連一條權值0的無向邊就好了;
程式碼如下(這是糖果一題中的不等關係,囊括了所有的不等關係)
for (int i=1;i<=m;++i)
{
int z=read(),x=read(),y=read();
if (z==1) add(x,y,0),add(y,x,0);
//z=1,表示第A個小朋友分到的糖果必須和第B個小朋友分到的糖果一樣多
else if (z==2)
{//z=2, 表示第A個小朋友分到的糖果必須少於第B個小朋友分到的糖果
if (x==y) { puts("-1"); exit(0); }
add(x,y,1);
}
else if (z==3) add(y,x,0);
//z=3,表示第A個小朋友分到的糖果必須不少於第B個小朋友分到的糖果
else if (z==4)
{//z=4,表示第A個小朋友分到的糖果必須多於第B個小朋友分到的糖果
if (x==y) { puts("-1"); exit(0); }
add(y,x,1);
}
else add(x,y,0);
//z=5,表示第A個小朋友分到的糖果必須不多於第B個小朋友分到的糖果
}
那麼我們建立好了這個差分系統時,
2.我們就可以跑一邊最長路了,
因為是最長路,
所以 dist[陣列 ]初始化要是負無窮大,
和最短路一樣但是是對立的,
如果有一個正權環 , 那麼我們肯定可以沿著這條正權環繞啊繞得到更長的最長路,
所以 不存在正確的k時就是不存在最長路的情況,
即比如a到b權1(a比b大) ,
b到c權1(b比c大),
而c到a也權1(c比a大) 自然是無解,
所以我們就跑 SPFA最長路+判負環就好了,
然後就是我們可以看到,
SPFA是求單源最短路徑,
而這道題並沒有明確的起點終點,
所以要得到每個點的d[]取最大值了,我們該怎麼做到呢?
這裡有一個小技巧了,
我們設定一個虛的源點0,
然後再從0到每個頂點連一條權值為0的有向邊,
程式碼
#include<bits/stdc++.h>
using namespace std;
const int maxn=1050;
const int maxm=20005;
const int inf=0x7fffffff;
struct rec
{
int y,z,Next;
rec()
{
y=z=Next=-1;
}
}edge[maxm];
int n, m, tot, ans, head[maxn], cnt[maxn];
inline int read()
{
int f=1,num=0;
char ch=getchar();
while (ch<'0'||ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
while (ch>='0'&&ch<='9') num=(num<<1)+(num<<3)+ch-'0', ch=getchar();
return num*f;
}
void add(int x,int y,int z)
{
edge[++tot].y=y,edge[tot].z=z,edge[tot].Next=head[x],head[x]=tot;
}
bool v[maxn];
int dist[maxn];
queue<int>q;
void spfa(int s)
{
for (int i=1;i<=n;i++) dist[i]=-inf;
memset(v,0,sizeof(v));
dist[s]=0,v[s]=1;
q.push(s);
cnt[s]++;
while (!q.empty())
{
int x=q.front();
q.pop(),v[x]=0;
for (int i=head[x];i!=-1;i=edge[i].Next)
{
int y=edge[i].y,z=edge[i].z;
if (dist[y]<dist[x]+z)//求最長路
{
dist[y]=dist[x]+z;
if (!v[y])
{
q.push(y),v[y]=1;
if (++cnt[y]>n)
{
cout<<"NO"<<endl;
exit(0);
}
}
}
}
}
for (int i=1;i<=n;i++)
ans=max(ans,dist[i]);
cout<<ans<<endl;
}
int main()
{
memset(head,-1,sizeof(head));
n=read(),m=read();
int x,y,z;
for (int i=1;i<=m;i++)
{
x=read(),y=read(),z=read();
if (z==-1) add(y,x,1);
else if (z==1) add(x,y,1);
else if (z==0) add(x,y,0),add(y,x,0);//即構建一個差分約束系統
}
for (int i=1;i<=n;i++)
add(0,i,0);//虛設源點
spfa(0);
return 0;
}
ok啦,這道題就解決了!!!!!!!!!