差分约束

P3275 [SCOI2011]糖果

差分约束的意味很浓了

差分约束就是 \(a-b\ge c\) 转化为 \(addedge(b,a,c)\) 然后跑最短路,判断是否有负环。有负环表示无解。

这个用三角形的基础知识可以证明。

这题直接按照上面的方法建边即可。可以建一个“超级起点”来简化题目并且保证了图的连通性。

如果求最大值,那么按小于等于建图后求最短路即可。如果求最小值,那么按小于等于建图后求最长路即可。

这题就直接跑最短路。但是这题卡掉了深搜版的差分约束。两种方法最好都了解一下。

广搜

#include<bits/stdc++.h>
using namespace std;
const int N=100010;
#define rint register int
int n,m,num_edge,head[N],dist[N],cnt[N];
long long ans;
bool inq[N];
struct edge{
	int to,nxt,val;
}e[N*3];
inline void add(int from,int to,int val)
{
	++num_edge;
	e[num_edge].nxt=head[from];
	e[num_edge].to=to;
	e[num_edge].val=val;
	head[from]=num_edge; 
}
inline bool spfa()
{
	queue<int>q;
	q.push(0);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		++cnt[u];inq[u]=false;
		if(cnt[u]>=n)return true;
		for(int i=head[u];i;i=e[i].nxt)
		{
			int v=e[i].to;
			if(dist[v]<dist[u]+e[i].val)
			{
				dist[v]=dist[u]+e[i].val;
				if(!inq[v]){q.push(v);inq[v]=true;}
			}
		}
	}
	return false;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(rint i=1,op,a,b;i<=m;++i)
	{
		scanf("%d%d%d",&op,&a,&b);
		if(op==1){add(b,a,0);add(a,b,0);}
		if(op==2){add(a,b,1);if(a==b){puts("-1");return 0;}}
		if(op==3){add(b,a,0);}
		if(op==4){add(b,a,1);if(a==b){puts("-1");return 0;}}
		if(op==5){add(a,b,0);} 
	}
	for(rint i=n;i>=1;--i)add(0,i,1);
	if(spfa()){puts("-1");return 0;}
	for(rint i=1;i<=n;++i)ans+=dist[i];
	printf("%lld\n",ans);
	return 0;
}

深搜

#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int n,m,num_edge,head[N],dist[N];
long long ans;
bool ins[N];
struct edge{
	int to,nxt,val;
}e[N*3];
void add(int from,int to,int val)
{
	++num_edge;
	e[num_edge].nxt=head[from];
	e[num_edge].to=to;
	e[num_edge].val=val;
	head[from]=num_edge; 
}
bool spfa(int u)
{
	ins[u]=true;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to,w=e[i].val;
		if(dist[v]<dist[u]+w)
		{
			dist[v]=dist[u]+w;
			if(ins[v])return true;
			if(spfa(v))return true;
		}
	}
	ins[u]=false;
	return false;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1,op,a,b;i<=m;++i)
	{
		scanf("%d%d%d",&op,&a,&b);
		if(op==1){add(b,a,0);add(a,b,0);}
		if(op==2){add(a,b,1);if(a==b){
		}}
		if(op==3){add(b,a,0);}
		if(op==4){add(b,a,1);}
		if(op==5){add(a,b,0);} 
	}
	for(int i=1;i<=n;++i)add(0,i,1);
	for(int i=0;i<=n;++i)
		ins[i]=false;
	dist[0]=0;
	if(spfa(0)){puts("-1");return 0;}
	for(int i=1;i<=n;++i)ans+=dist[i];
	printf("%d\n",ans);
	return 0;
}

POJ1275 Cashier Employment

差分约束的经典,如果很快能推出所有的约束条件,%%%

发现这题如果知道用几个人就很好做了,因为约束条件总是不够,加上人数就可以差分约束了。而且人数还是有上下界的:[0,n] ,于是二分。

现在已经有了人数,再来差分约束,设第i小时开始工作人数为a[i],每小时至少r[i]个人工作,有t[i]个人愿意在i小时开始工作,那么就有

\(0\le a[i]\le t[i]\)

\(a[i]+a[i-1]+\cdots +a[i-7]\ge r[i] (i\ge 9)\)

\(a[i+17]+a[i+18]+\cdots +a[23]+a[1]+a[2]+\cdots +a[i]\ge r[i] (1\le i \le 8)\)

但是这样还是没法差分约束,变量太多了。但是变量下标都是连续的\(\to\)前缀和!

\(s[i]=\sum\limits_{j=1}^{i} a[j]\) ,带入上面的式子:

\(s[i]-s[i-1]\ge 0\)

\(s[i]-s[i-1]\le t[i]\)

\(s[i]-s[i-8]\ge r[i] (i\ge 9)\)

\(s[i]+s[23]-s[i+16]\ge r[i](1\le i \le 8)\)

转换成最长路看看有没有正环就好了。

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int r[30],n,t,T,ans,head[30],num_edge,num[30],cnt[30],dist[30];
bool inq[30];
struct edge{
	int to,nex,val;
}e[100];
void add(int from,int to,int val)
{
	++num_edge;
	e[num_edge].nex=head[from];
	e[num_edge].to=to;
	e[num_edge].val=val;
	head[from]=num_edge;
}
bool spfa(int x)
{
	queue<int>q;
	for(int i=0;i<=24;++i)
	{
		inq[i]=0;
		cnt[i]=0;
		dist[i]=-999999999;
	}
	q.push(0);
	inq[0]=true;
	dist[0]=0;
	cnt[0]=1;
	while(!q.empty())
	{
		int u=q.front();q.pop();
		inq[u]=false;
		for(int i=head[u];i;i=e[i].nex)
		{
			int v=e[i].to,w=e[i].val;
			if(dist[v]<dist[u]+w)
			{
				dist[v]=dist[u]+w;
				if(!inq[v])
				{
					q.push(v);
					inq[v]=true;
				}
				++cnt[v];
				if(cnt[v]>=25)return false;
			}
		}
	}
	return dist[24]==x;
}
bool check(int x)
{
	num_edge=0;
	memset(head,0,sizeof(head));
	add(0,24,x);
	for(int i=1;i<=24;++i)add(i-1,i,0);
	for(int i=1;i<=24;++i)add(i,i-1,-num[i]);
	for(int i=9;i<=24;++i)add(i-8,i,r[i]);
	for(int i=1;i<=8;++i) add(i+16,i,r[i]-x);
	if(spfa(x))return true;
	else return false;
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		ans=999999999;
		memset(num,0,sizeof(num));
		for(int i=1;i<=24;++i)scanf("%d",&r[i]);
		scanf("%d",&n);
		for(int i=1;i<=n;++i)scanf("%d",&t),++num[t+1];
		int ll=0,rr=n;
		while(ll<=rr)
		{
			int mid=(ll+rr)>>1;
			if(check(mid))ans=mid,rr=mid-1;
			else ll=mid+1;
		}
		if(ans!=999999999)printf("%d\n",ans);
		else puts("No Solution");
	}
	return 0;
}

原文地址:https://www.cnblogs.com/zzctommy/p/12321663.html