zoukankan      html  css  js  c++  java
  • 图论7-差分约束系统

    抱歉图论两天没更了,今天更一个差分约束,这也是比较有用的一个知识点吧。

    差分约束系统是什么呢?给定一些约束条件,然后求出有/没有符合这个约束条件的对其中每个元素的取值,当然,也能算具体的值是多少。

    对了,看这篇博客之前先学会SPFA算法。一道例题:luoguP1993

    题目大意:有n个变量和一些关系条件,每一种关系条件都形如以下:a>=b+x,a<=b+x或a=b,x是个常数,问这些关系条件是否有矛盾。

    差分约束解析:差分约束中的约束条件就是和这道题的条件的形式一样,比如a>=b+x,看到这个,有没有想起SPFA中更新dis[y]是的式子

    dis[y]<=dis[x]+val[i]?如果有a<=b+x是不是就能建一个从b到a的路径,权值为x,然后跑最短路就行了。如果建边的那块没有看懂也没事

    可以看下面那张图片,清楚的解释了差分约束系统的建边。

    看完这个图之后应该就明白了差分约束的建边原理了,但是还有一类边a>=b+x怎么建呢?很好办,做一个数学转化,变成b<=a+(-x),

    是不是现在会建了?就建一条从b到a长度为-x的边就行了。但是这道题中的判断是否成立却有一点难度,怎么叫不成立呢?是不是就是

    先说a>=b+x再说a<=b+y且y<x,再看最短路中,就是先建了a->b,-x再建b->a,y,又因为y<x,所以y-x<0所以这种情况就会出现负环

    还有几种不成立都会导致负环的产生,所以可以用spfa判一波负环,如果有负环,输出NO,否则输出YES。代码如下:

    #include<bits/stdc++.h>
    #define xms cte
    #define int long long
    using namespace std;
    const int NR=10005;
    const int INF=1e18+10;
    int n,m,s;
    int read()
    {
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
        return x*f;
    }
    int to[NR*3],nxt[NR*3],val[NR*3];
    int head[NR];
    int tot=1;
    void add(int x,int y,int z)
    {
        to[tot]=y;
        val[tot]=z;
        nxt[tot]=head[x];
        head[x]=tot++;
    }
    bool flag=0;
    int dis[NR],cnt[NR];
    int cnt2[NR];
    bool vis[NR];
    void SPFA()
    {
        for(int i=0;i<=n;i++) dis[i]=-1e18;
        queue<int> q;
        q.push(s);
        vis[s]=1,dis[s]=0;
        while(!q.empty())
        {
            int x=q.front();
            q.pop();
    //        cout<<x<<endl;
            cnt2[x]++;
            vis[x]=0;
            if(cnt2[x]>=n)
            {
                flag=1;
                return;
            }
            for(int i=head[x];i;i=nxt[i])
            {
                int y=to[i];
                if(dis[y]<dis[x]+val[i])
                {
                    
                    dis[y]=dis[x]+val[i];
                    if(!vis[y])
                    {
                        q.push(y);
                        vis[y]=1;
                    }
                }
            }
        }
    }
    signed main()
    {
    //    freopen("farm.in","r",stdin);
    //    freopen("farm.out","w",stdout);
        n=read(),m=read();
        for(int i=1;i<=m;i++)
        {
            int op;
            op=read();
            if(op==1)
            {
                int a=read(),b=read(),c=read();
                add(a,b,c);
            }
            if(op==2)
            {
                int a=read(),b=read(),c=read();
                add(b,a,-c);
            }
            if(op==3)
            {
                int a=read(),b=read();
                add(a,b,0);
                add(b,a,0);
            }
        }
        for(int i=1;i<=n;i++) add(0,i,0);
        SPFA();
        if(!flag) puts("Yes");
        else puts("No");
        return 0;
    }

    注意在洛谷上提交时必须开O2优化,否则洛谷卡bfs版的spfa

    然后,基础的差分约束讲完了,现在来讲一些进阶的。

    如果发现这个约束条件没有那么容易怎么办呢?下面我来推荐一道题:POJ1275

    题目大意:有一家24小时超市,每个小时最少需要a[i]个员工,有n(n<=1000)个应聘者,每个人都有一个参数x,表示从第x之后8个小时,

    这个人能工作,问招聘多少人能满足要求。如果不能满足,则输出No solution。

    题目解析:这道题如果不说是差分约束是不是很那想到啊?这道题怎么差分约束呢?首先要确定节点是什么,然后再想别的。我们用s数组

    表示前缀和,s[i]表示所有i天之前工作者的数量,易得s[i]-s[i-8]>=a[i],但当i<7时,就会涉及三元不等式,为了解决这个问题,我们可以

    枚s[23],并将i<7时变成二元不等式,然后做spfa就行了。建议大家先不看代码,自己写一写这道题。

    但我还是将代码放在这里吧

    #include<bits/stdc++.h>
    using namespace std;
    const int NR=105;
    const int MR=2e3+10;
    int n;
    int a[NR],num[NR];
    int to[MR],nxt[MR],val[MR];
    int head[NR];
    int tot=1;
    void add(int x,int y,int z)
    {
    	to[tot]=y;
    	val[tot]=z;
    	nxt[tot]=head[x];
    	head[x]=tot++;
    }
    int read()
    {
    	int x=0,f=1;char ch=getchar();
    	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    	while(ch<='9'&&ch>='0'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    	return x*f;
    }
    bool vis[NR];
    int dis[NR];
    void Init()
    {
    	tot=1;
    	memset(head,0,sizeof(head));
    	memset(vis,0,sizeof(vis));
    	memset(dis,-0x3f,sizeof(dis));
    }
    void build(int x)
    {
    	for(int i=0;i<=23;i++)
    	{
    		add(i,i+1,0);
    		add(i+1,i,-num[i]);
    	}
    	for(int i=7;i<=23;i++) add(i-7,i+1,a[i]);
    	add(0,24,x),add(24,0,-x);
    	for(int i=0;i<=6;i++) add(i+17,i+1,a[i]-x);
    }
    bool check(int x)
    {
    	Init();
    	build(x);
    	queue<int> q;
    	q.push(0),dis[0]=0,vis[0]=1;
    	while(!q.empty())
    	{
    		int now=q.front();q.pop();vis[now]=0;
    		if(now==24&&dis[now]>x) return 0;
    		for(int i=head[now];i;i=nxt[i])
    		{
    			int y=to[i];
    			if(dis[y]<dis[now]+val[i])
    			{
    				dis[y]=dis[now]+val[i];
    				if(!vis[y])
    				{
    					q.push(y);
    					vis[y]=1;
    				}
    			}
    		}
    	}
    	return dis[24]==x;
    }
    void solve()
    {
    	int ans=-1;
    	for(int i=0;i<=n;i++)
    	{
    		if(check(i))
    		{
    			ans=i;
    			break;
    		}
    	}
    	if(ans<0) puts("No Solution");
    	else printf("%d
    ",ans);
    }
    int main()
    {
    //	freopen("1.in","r",stdin);
    //	freopen("1.out","w",stdout);
    	int T=read();
    	while(T--)
    	{
    		memset(num,0,sizeof(num));
    		for(int i=0;i<=23;i++)
    		{
    			a[i]=read();
    		}
    		n=read();
    		for(int i=1;i<=n;i++)
    		{
    			int x=read();
    			num[x]++;
    		}
    		solve();
    	}
    	return 0;
    }
    

      

  • 相关阅读:
    关于数组的一些面试题目及答案
    java类的定义以及参数传递
    第39届大学生程序设计大赛亚洲区域赛广州站比赛总结
    模拟退火法(吊打XXX)Bzoj3680
    树链剖分(单点更新,求区间最值,区间求和Bzoj1036)
    树链剖分(线段树区间更新求和(lazy操作)hdu3966)
    最大权闭合图(Road constructions)hdu3917
    最大权闭合图最大获益(把边抽象为点)HDU3879
    最大权闭合图hdu3996
    最大密集子图(01分数规划+二分+最小割)POJ3155
  • 原文地址:https://www.cnblogs.com/chen-1/p/12608187.html
Copyright © 2011-2022 走看看