差分约束 学习笔记

一直只会差分的我开始学习差分约束了!资瓷资瓷!

什么是差分约束呢?它旨在解决不等式组求最值的问题。

比如说:HZOI2016 奖学金 WC模拟 奶牛排队

它把问题转化成图论_最短路模型来解决,把数学问题转化在图上,利用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;
}

 

 

2.POJ 1275 Cashier Employment

题目大意:店里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;
}

 

 

 

 

 还有一道题叫做服务器储存信息问题,是一道有难度的题目,但是我打算为它单独写一篇博客。

如果没有看到的话,大概是因为我忘记这件事了还没写,因为我困了... ...

 

 

posted @ 2017-05-18 13:14  Fenghr  阅读(306)  评论(0编辑  收藏  举报