差分约束小结(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(解题报告

详见结题报告


POJ3159 Candies

这个题不卡建图,所有约束关系都给你了,只要根据给的不等式建边就好,不过,用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

黑书上的例题,老经典了,也老难了,我纠结了好久才出来……

这题建图有难度,一般不容易想到所有的约束条件,看了黑书讲解,然后又搜了报告,才做出来的,所以不贴我丑陋的代码了,给个解题报告看看吧,挺不错的。


感谢:

http://hi.baidu.com/accplaystation/blog/item/7c6d10136ef28b856438db6b.html

http://happylch21.blog.163.com/blog/static/165639759201163084924988/

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值