差分约束 学习笔记
一直只会差分的我开始学习差分约束了!资瓷资瓷!
什么是差分约束呢?它旨在解决不等式组求最值的问题。
它把问题转化成图论_最短路模型来解决,把数学问题转化在图上,利用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; }
还有一道题叫做服务器储存信息问题,是一道有难度的题目,但是我打算为它单独写一篇博客。
如果没有看到的话,大概是因为我忘记这件事了还没写,因为我困了... ...