差分约束 学习笔记
一直只会差分的我开始学习差分约束了!资瓷资瓷!
什么是差分约束呢?它旨在解决不等式组求最值的问题。
它把问题转化成图论_最短路模型来解决,把数学问题转化在图上,利用BF算法或SPFA。
注意到SPFA的松弛条件:
x ->(Eval) y : if( far[x]+Eval<far[y] ) {... ...}
就是说跑完之后总有 far[x]+Eval>=far[y] ;
转换一下得到:far[x]-far[y]>=-Eval;
相当于满足一个二元一次不等式?
而且长得很像 x-y>=z 的情况?
也就是说,如果你列出了一系列不等式 {
S[1]-S[2]<a;
S[2]-S[3]<=b;
S[3]-S[4]>=c;
...
}
你就可以转化成一下不等式 {
S[2]-S[1]>=1-a;
S[3]-S[2]>=-b;
S[3]-S[4]>=c;
...
}
根据上面的推论,出现x-y>=-z,就从x到y连一条边权为z的边,然后用BF或者SPFA跑最短路就吼辣!
出现无解的话就是跑出了负权环,SPFA松弛次数超过n次就是了。
注意求最大值和最小值时的连边是不一样的,自己注意一下就好了。
正确性是可以保证的(但是我好像在这个算法上面看到了影射学的感觉?)。
这种鬼里鬼气的算法一般都是套路,出现了不是套路的题目就只能重新分析或者GG了。
板子都是很好打的,一个连边一个SPFA就可以完事了。但是实际运用起来,就不是那么可爱了。
下面我来搞几道题目,讲几个套路。
1.POJ1201 Intervals题目大意:给你n组约束(l,r,c),表示区间 [l,r] 上至少有c个点。问你一共至少有多少个点?
这道题还是很simple的,记一个前缀和S[i]表示 [0,i] 上有多少个点。
然后一个约束就代表 S[r]-S[l-1]>-c;
还有两个比较隐含的条(tao)件(lu):
S[i+1]-S[i]>=0;
S[i+1]-S[i]<=1;
连完边跑一遍就好了,负权环都不要判。
总结:差分约束的常见套路—前缀和,相邻点的约束。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <cstring>
#include <queue>
#define LL long long int
#define ls (x << 1)
#define rs (x << 1 | 1)
using namespace std;
const int N = 50010;
struct Node{int to,val,next;}E[N<<4];
int far[N],head[N],tot,In[N],m,MIN=N,MAX=0;
int gi()
{
int x=0,res=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*res;
}
LL gL()
{
LL x=0,res=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*res;
}
inline void link(int u,int v,int c)
{
E[++tot]=(Node){v,c,head[u]};
head[u]=tot;
}
inline void SPFA()
{
memset(far,127/3,sizeof(far));
queue<int>Q;Q.push(MAX);
far[MAX]=0;In[MAX]=1;
while(!Q.empty()){
int x=Q.front();Q.pop();
for(int e=head[x];e;e=E[e].next){
int y=E[e].to;
if(far[x]+E[e].val<far[y]){
far[y]=far[x]+E[e].val;
if(!In[y])Q.push(y),In[y]=1;
}
}
In[x]=0;
}
printf("%d\n",-far[MIN]);
}
int main()
{
//freopen("input.txt","r",stdin);
//freopen("output.txt","w",stdout);
m=gi();
while(m--){
int u=gi(),v=gi(),c=gi();
link(v,u-1,-c);
MIN=min(MIN,u-1);
MAX=max(MAX,v);
}
for(int i=MIN;i<=MAX;++i){
link(i,i+1,1);
link(i+1,i,0);
}
SPFA();
/*fclose(stdin);
fclose(stdout);*/
return 0;
}
题目大意:店里24小时要求至少有R[i]个服务员,一个服务员可以连续工作8小时,现有n个服务员来应聘,告诉你他们开始工作的时间,求最少要多少个人。
又是显然的,前缀和差分。
记S[i]表示前i个小时一共招募了多少个人,T[i]表示该时间来应聘的人数。
S[-1]=0;
那么就有一些很好的不等式了 {
S[i]-S[i-1]>=0;
S[i]<=S[i-1]+t[i];
S[i]-S[j]>=R[i], i>j 且 i=j+8 mod 24;
}
但似乎剩下的写不下去了?好像要知道S[24]的值?
没事我们可以枚举这个值,记为Ans;
于是又有 {
S[i]-S[j]>=R[i]-Ans, i<j 且 i=j+8 mod 24;(可以推一下)
不要忘了 S[23]-S[-1]>=Ans;(实际上应该是=)
}
怎么判断有没有解呢?出现了负权环就是无解了。
这么枚举肯定会T的。显而易见的这个Ans有可二分性,可以就把枚举改成二分。
总结:二分答案+差分约束判负环是个优秀的东西。
其实判负环没必要卡n次,卡到10~log次就差不多了惹。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <cstring>
#include <queue>
#define LL long long int
#define ls (x << 1)
#define rs (x << 1 | 1)
using namespace std;
const int N = 1010;
struct Node{int to,val,next;}E[N*N];
int n,m,head[N],tot,Ans,R[N],T[N];
int In[N],far[N],num[N];
int gi()
{
int x=0,res=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*res;
}
inline void link(int u,int v,int c)
{
u++;v++;
E[++tot]=(Node){v,c,head[u]};
head[u]=tot;
}
inline void addedge(int ans)
{
memset(head,0,sizeof(head));tot=0;
link(23,-1,-ans);link(-1,23,ans);
for(int i=0;i<24;++i)
link(i,i-1,0),link(i-1,i,T[i]);
for(int j=0;j<24;++j){
int i=(j+8)%24;
if(i>j)link(i,j,-R[i]);
else link(i,j,ans-R[i]);
}
}
inline bool SPFA()
{
memset(In,0,sizeof(In));
memset(num,0,sizeof(num));
memset(far,127/3,sizeof(far));
queue<int>Q;Q.push(0);far[0]=0;
while(!Q.empty()){
int x=Q.front();Q.pop();In[x]=0;
for(int e=head[x];e;e=E[e].next){
int y=E[e].to;
if(far[x]+E[e].val<far[y]){
if(++num[y]>20)return false;
far[y]=far[x]+E[e].val;
if(!In[y])Q.push(y),In[y]=1;
}
}
}
return true;
}
int main()
{
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
int Case=gi();
while(Case--){
memset(T,0,sizeof(T));
for(int i=0;i<24;++i)R[i]=gi();
int l=1,r=m=Ans=gi();
for(int i=1;i<=m;++i)T[gi()]++;
/*for(int i=l;i<=r;++i){
addedge(i);
if(SPFA())break;
else Ans=i+1;
}
*/
while(l<=r){
int mid=(l+r)>>1;
addedge(mid);
if(SPFA())r=mid-1;
else l=mid+1;
}
if(l>m)printf("No Solution\n");
else printf("%d\n",l);
}
return 0;
}
还有一道题叫做服务器储存信息问题,是一道有难度的题目,但是我打算为它单独写一篇博客。
如果没有看到的话,大概是因为我忘记这件事了还没写,因为我困了... ...

浙公网安备 33010602011771号