1. 程式人生 > >差分約束小結(poj1201,poj1716,poj1364,poj3159,poj3169,poj1275)

差分約束小結(poj1201,poj1716,poj1364,poj3159,poj3169,poj1275)

總體感覺,難點是建圖,因為建圖的時候需要考慮一些題目上沒有明確給出的隱含條件,只有把所有約束關係找全之後,然後再正確運用最短路(或者最長路)的性質求解,才能得到正確答案。

說說我的收穫:

node1:對於區間放置元素問題,要注意區間開閉性,也就是說要關注對點的約束。特別注意每個點上放置元素個數的限制,這裡一般都是隱含關係的考察點(詳見下文)。

node2:對於差分不等式,a - b <= c ,建一條 b 到 a 的權值為 c 的邊,求的是最短路,得到的是最大值;對於不等式 a - b >= c ,建一條 b 到 a 的權值為 c 的邊,求的是最長路,得到的是最小值。存在負環的話是無解,求不出最短路(dist[ ]沒有得到更新)的話是任意解。

node3:建圖中有時候會用到一個虛點,這個點到圖中每個實點的距離(dist[ ])為0,當然這個點的作用是方便圖中的點入隊(spfa演算法),然後使這些實點的dist[ ]值得到更新,其實有時候我們可以省略這個點,手動把所有實點入隊,同時更新這些實點的 dist[ ] 值和 visit[ ] 值。

POJ1201/ZOJ1508/HDU1384 Intervals(解題報告

這道題就是整數區間問題,典型的差分約束問題,題目要求了每個點要麼不放元素,要麼放一個元素,那麼我們就可以根據這個要求加邊:0 <= s[ i+1 ] - s[ i ] <= 1 。這就是上面說的隱含條件的應用。

POJ1716 Integer Intervals

這個題是上面那個的閹割版,把每個區間端點的大小關係設為了定值,其他的一樣。

#include<cstdio>
#include<cstring>
#include<climits>
#include<algorithm>
#include<queue>
#define min(a,b) a<b?a:b
#define max(a,b) a>b?a:b
using namespace std;

const int N = 10010;

struct Edge{
	int s,e,v,next;
}edge[3*N];

int m,e_num,left,right,head[N],vis[N],dist[N];

void AddEdge(int a,int b,int c){
	edge[e_num].s=a; edge[e_num].e=b; edge[e_num].v=c;
	edge[e_num].next=head[a]; head[a]=e_num++;
}

queue <int> q;

void getmap(){
	int i,a,b;
	e_num=0; left=INT_MAX; right=INT_MIN;
	memset(head,-1,sizeof(head));
	while(m--){
		scanf("%d%d",&a,&b);
		AddEdge(a,b+1,2);
		left=min(left,a);
		right=max(right,b);
	}
	for(i=left;i<=right;i++){
		AddEdge(i,i+1,0);
		AddEdge(i+1,i,-1);
	}
	for(i=left;i<=right;i++){
		q.push(i);
		vis[i]=1;
		dist[i]=0;
	}
}

void spfa(){
	int i;
	while(!q.empty()){
		int cur=q.front();
		q.pop();
		vis[cur]=0;
		for(i=head[cur];i!=-1;i=edge[i].next){
			int u=edge[i].e;
			if(dist[u]-dist[cur]<edge[i].v){
				dist[u]=dist[cur]+edge[i].v;
				if(!vis[u]){
					q.push(u); vis[u]=1;
				}
			}
		}
	}
	printf("%d\n",dist[right+1]);
}

int main()
{
	while(~scanf("%d",&m))
	{
		getmap();
		spfa();
	}
	return 0;
}


POJ1364/ZOJ1260 King(解題報告

詳見結題報告

這個題不卡建圖,所有約束關係都給你了,只要根據給的不等式建邊就好,不過,用spfa的話,需要注意,佇列會超時的……

貌似是出資料的故意這麼玩的,改成棧吧,由於棧和隊的性質差異(一個後進先出,一個先進先出),所以對於卡佇列的資料用棧可以秒殺,當然反過來也一樣,我這個題開始用的是佇列spfa,超時了,聽大神 XH 指點,把隊改成棧,就A了,效率還不錯。當然作為一個“正直”的ACMer而言,我們不能假設資料是仁慈的,所以用 heap 吧,旱澇保收。

程式碼:

#include<cstdio>
#include<stack>
#include<climits>
#include<cstring>
using namespace std;
const int N = 30010;

struct Edge{
	int s,e,next,v;
}edge[5*N];

int e_num,dist[N],vis[N],head[N];

void AddEdge(int a,int b,int c){
	edge[e_num].s=a; edge[e_num].e=b; edge[e_num].v=c;
	edge[e_num].next=head[a]; head[a]=e_num++;
}

void spfa(int n){
	int i;
	stack <int>q;
	memset(vis,0,sizeof(vis));
	for(i=1;i<=n;dist[i++]=INT_MAX);
	
	q.push(1);vis[1]=1;
	dist[1]=0;

	while(!q.empty()){
		int cur=q.top();
		q.pop();
		vis[cur]=0;
		for(i=head[cur];i!=-1;i=edge[i].next){
			int u=edge[i].e;
			if(dist[u]-dist[cur]>edge[i].v){
				dist[u]=dist[cur]+edge[i].v;
				if(!vis[u]){
					q.push(u);
					vis[u]=1;
				}
			}
		}
	}
	printf("%d\n",dist[n]);
}

int main()
{
	int n,m,a,b,c;
	scanf("%d%d",&n,&m);
	e_num=0;
	memset(head,-1,sizeof(head));
	while(m--){
		scanf("%d%d%d",&a,&b,&c);
		AddEdge(a,b,c);
	}
	spfa(n);
	return 0;
}

POJ3169 Layout

這個題目,對於點上的元素個數,特意給出了說明,可以有任意多個元素在同一個點上,所以不用加邊,直接建圖求最短路即可。

程式碼:

#include<cstdio>
#include<cstring>
#include<climits>
#include<algorithm>
#include<queue>
using namespace std;

const int N = 1010;
struct Edge{
	int s,e,v,next;
}edge[20*N];

int n,ml,md,e_num,head[N],vis[N],dist[N],countx[N];

void AddEdge(int a,int b,int c){
	edge[e_num].s=a; edge[e_num].e=b; edge[e_num].v=c;
	edge[e_num].next=head[a]; head[a]=e_num++;
}

void getmap(){
	int i,a,b,c;
	e_num=0;
	memset(head,-1,sizeof(head));
	while(ml--){
		scanf("%d%d%d",&a,&b,&c);
		AddEdge(a,b,c);
	}
	while(md--){
		scanf("%d%d%d",&a,&b,&c);
		AddEdge(b,a,-c);
	}
	for(i=2;i<=n;i++)
		AddEdge(i,1,0);
}

void spfa(){
	int i;
	queue <int> q;

	memset(vis,0,sizeof(vis));
	memset(countx,0,sizeof(countx));
	for(i=1;i<=n;dist[i++]=INT_MAX);

	dist[1]=0; vis[1]=1; countx[1]++;
	q.push(1);

	int tmp=0;
	while(!q.empty()){
		int cur=q.front();
		q.pop();
		vis[cur]=0;
		if(countx[cur]>n){
			tmp=-1;break;
		}
		for(i=head[cur];i!=-1;i=edge[i].next){
			int u=edge[i].e;
			if(dist[u]-dist[cur]>edge[i].v){
				dist[u]=dist[cur]+edge[i].v;
				if(!vis[u]){
					q.push(u); vis[u]=1;
					countx[u]++;
				}
			}
		}
	}
	if(tmp==-1)printf("%d\n",tmp);
	else printf("%d\n",dist[n]<INT_MAX?dist[n]:-2);
}

int main()
{
	scanf("%d%d%d",&n,&ml,&md);
	getmap();
	spfa();
	return 0;
}

POJ1275/ZOJ1420/HDU1529 Cashier Employment

黑書上的例題,老經典了,也老難了,我糾結了好久才出來……

這題建圖有難度,一般不容易想到所有的約束條件,看了黑書講解,然後又搜了報告,才做出來的,所以不貼我醜陋的程式碼了,給個解題報告看看吧,挺不錯的。

感謝: