1. 程式人生 > >p1227 關係運算圖

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啦,這道題就解決了!!!!!!!!!