zoukankan      html  css  js  c++  java
  • 差分约束

    什么是差分约束系统?

    差分约束系统是一种特殊的(N)元一次不等式组,它包含(N)个变量(x_1 dots x_N)以及(M)个约束条件,每个约束条件都是由两个变量作差构成的,形如(x_i-x_jle c_k),其中(c_k)是常数,(1le i, jle N, 1le k le M)。我们要解决的问题是:求一组解(x_1=a_1, x_2=a_2, dots, x_N=a_N),使所有约束条件都得到满足。


    求解方法

    一句话:转化为图模型,然后用最短路或最长路求解,计算最长路和最短路是一样的,就是大、小于号换一下方向就行。

    • 约束条件(x_i-x_jle c_k)可以变形为(x_ile x_j+c_k),这与单元最短路径问题中的三角不等式(松弛操作)(dis[y]le dis[x]+z)非常相似,我们可以把这个约束条件转化为一条从(j)(x)长度为(c_k)的边,简单地说,可以用(j)点松弛(i)点。我们对这个图计算所有点的最短路,就得到了满足一系列约束条件的最小值。
    • 约束条件(x_i-x_jge c_k)可以变形为(x_ige x_j+c_k),这与单元最短路径问题中的三角不等式(绷紧操作)(dis[y]ge dis[x]+z)非常相似,我们可以把这个约束条件转化为一条从(j)(i)长度为(c_k)的边,简单地说,可以用(j)点绷紧(i)点。我们对这个图计算所有点的最长路,就得到了满足一些列约束条件的最大值。

    举例说明1:

    • b比a多最少2颗糖,c比b多最少3颗糖,c比a最少多4颗糖,d比c至少多5颗糖

    • 转换为公式:$$b-age 2 $$ $$c-bge3 $$ $$c-age 4$$ $$d-cge 5$$

    • 公式变形为:$$bge a+2 $$ $$cge b+3$$ $$cge a+4$$ $$dge c+5$$

    • 转换为图:

    • 这时可以发现,从a到c的最长路才能满足约束条件

    • 从a开始计算最长路:dis[a]=0, dis[b]=2, dis[c]=5, dis[d]=10

    • 现在得到的就是满足条件的一组解,而且这是满足约束条件的最小值


    举例说明2:

    • b比a最多多2颗糖,c比b最多多3颗糖,c比a最多多6颗糖,d比c最多多5颗糖

    • 转换为公式:$$b-ale 2 $$ $$c-ble3 $$ $$c-ale 6$$ $$d-cle 5$$

    • 公式变形为:$$ble a+2 $$ $$cle b+3$$ $$cle a+6$$ $$dle c+5$$

    • 转换为图:

    • 这时可以发现,从a到c的最短路才能满足约束条件

    • 从a开始计算最短路:dis[a]=0, dis[b]=2, dis[c]=5, dis[d]=10

    • 现在得到的就是满足条件的一组解,而且这是满足约束条件的最大值


    解题过程:

    1. 根据要求规整约束条件,如果要求一组最小值,就全部规整为 (x_i-x_jge c_k) 的形式,然后转化为从(j)(i)的长为(c_k)的边;如果要求一组最大值,就全部规整为 (x_i-x_jle c_k) 的形式,然后转化为从(j)(i)的长为(c_k)的边;完成构图。
    2. 如果只是要判定是否有解,两种形式都可以,但一定是同一种类型。公式是可以按数学方式转换的,如:(a-bge k)可以转换为(b-ale-k)
    3. 对于“a和b相等”这种条件,可以理解为(a-bge0, b-age0)(a-ble0, b-ale0),简单说,就是构建两条长为0的双向边。
    4. 求最小值的话,对图计算最长路;求最大值的话,对图计算最短路。因存在负边,要使用SPFA算法。
    5. 对于BFS-SPFA和DFS-SPFA,在大多数题中,似乎DFS-SPFA效率更好,如果是判环更是如此。

    无解的判定1

    • 对于形如 (x_i-x_jle c_k) 的约束系统,求最短路,如果存在负环,则无解

    • 如:b比a最多多2颗糖,c比b最多多3颗糖,c比a至少多6颗糖(很明显无解)

    • 转换为公式:$$b-ale 2 $$ $$c-ble3 $$ $$c-age 6$$

    • 公式变形为:$$ble a+2 $$ $$cle b+3$$ $$ale c-6$$

    • 转换为图:

    • 可见,图中有负环,无解


    无解的判定2

    • 对于形如 (x_i-x_jge c_k) 的约束系统,求最长路,如果存在正环,则无解

    • 如:b比a多最少2颗糖,c比b多最少3颗糖,c比a最多多4颗糖(很明显无解)

    • 转换为公式:$$b-age 2 $$ $$c-bge3 $$ $$c-ale 4$$

    • 公式变形为:$$bge a+2 $$ $$cge b+3$$ $$age c-4$$

    • 转换为图:

    • 可见,图中有正环,无解


    关于解的数量

    如果(x_1, x_2, dots, x_n)是一组解,很显然(x_1+k, x_2+k, dots, x_3+k)也是一组解,因此在没有特别限定的情况下,差分约束系统要么无解,要么就有无穷多的解。


    处理非连通图:

    通过差分约束方程构建的图可能是不连通的,你可以从每个点开始一轮SPFA,如:

    for(int i=1; i<=N; i++)
        if(!vis[i]) SPFA(i);
    

    另一种更常见的作法是构建一个超级点,让这个点连到图上的所有点,然后从超级点开始SPFA。

    for(int i=N; i>=1; i--)
        add_edge(N+1, i, LEN);
    SPFA(N+1);
    //N+1号点是超级点
    //为什么从N循环到1呢?自己做题就会发现问题。
    //LEN是统一的边长,设置的值可以为0,1或-1,取决于dis数组的初始值以及你是松弛操作还是“绷紧”操作
    //“绷紧”这个词是我自己发明的。
    

    这个超级结点会破坏原来的约束性吗?不会!因为超级结点(x_0)的含义是这样的:

    (x_1-x0ge LEN, x_2-x0ge LEN, x_3-x0ge LEN, dots, x_n-x0ge LEN)

    或者

    (x_1-x0le LEN, x_2-x0le LEN, x_3-x0le LEN, dots, x_n-x0le LEN)

    很显然,超级结点的工作就只是把大家连起来就行了。最后提醒,要想让松弛和绷紧操作启动,LEN值一定要合理设置!

    下面是两个模板题的代码。


    洛谷P1993小K的农场BFS-SPFA版
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<queue>
    using namespace std;
    const int maxn=10010, maxm=10010;
    struct edge{int t, w; edge *nxt; edge(int to, int val, edge *next){t=to, w=val, nxt=next;} };
    edge *h[maxn];
    void add(int u, int v, int w){ h[u]=new edge(v, w, h[u]); }
    int n, m, flag, dis[maxn], instack[maxn];			//flag标记是否有负环
    int inq[maxn], cnt[maxn]; 
    bool SPFA(int x)
    {
    	queue<int> Q;
    	Q.push(x);
    	inq[x]=1, cnt[x]=1;
    	while(!Q.empty())
    	{
    		int from=Q.front();
    		Q.pop();
    		inq[from]=0;
    		for(edge *p=h[from]; p; p=p->nxt)
    		{
    			int to=p->t, w=p->w;
    			if(dis[to]>dis[from]+w)
    			{
    				dis[to]=dis[from]+w;
    				cnt[to]=cnt[from]+1;
    				if(cnt[to]>n+1)	return false;		//false有负环 
    				if(!inq[to])
    				{
    					Q.push(to);
    					inq[to]=1;
    				}
    			}
    		}
    	}
    	return true;
    }
    int main()
    {
    	scanf("%d%d", &n, &m);
    	for(int i=1, t, a, b, c; i<=m; i++)
    	{
    		scanf("%d", &t);
    		if(t==1)	scanf("%d%d%d", &a, &b, &c), add(a, b, -c);
    		if(t==2)	scanf("%d%d%d", &a, &b, &c), add(b, a, c);
    		if(t==3) 	scanf("%d%d", &a, &b), add(a, b, 0), add(b, a, 0);
    	}
    	for(int i=1; i<=n; i++)	add(n+1, i, -1); 		//加入一个指向所有点的超级结点,使图“联通”起来
    													//从n+1号点开始,只有负边才能启动松弛,所以,加入的边为-1
    	flag=SPFA(n+1);
    	if(flag)	printf("Yes
    ");
    	else		printf("No
    ");
    	return 0;
    }
    
    洛谷P1993小K的农场DFS-SPFA版,在差分约束类问题中,建议使用DFS的SPFA
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int maxn=10010, maxm=10010;
    struct edge{int t, w; edge *nxt; edge(int to, int val, edge *next){t=to, w=val, nxt=next;} };
    edge *h[maxn];
    void add(int u, int v, int w){ h[u]=new edge(v, w, h[u]); }
    int n, m, flag, dis[maxn], instack[maxn];			//flag标记是否有负环 
    void SPFA(int x)
    {
    	instack[x]=1;
    	for(edge *p=h[x]; p; p=p->nxt)
    	{
    		if(dis[p->t]>dis[x]+p->w)					//因dis的初始值都是0,只有负边可以启动松弛,这样避免了大量无用的松弛操作
    		{
    			if(instack[p->t])
    			{
    				flag=true;
    				return;
    			}
    			dis[p->t]=dis[x]+p->w;
    			SPFA(p->t);
    		}
    	}
    	instack[x]=0;
    }
    int main()
    {
    	scanf("%d%d", &n, &m);
    	for(int i=1, t, a, b, c; i<=m; i++)
    	{
    		scanf("%d", &t);
    		if(t==1)	scanf("%d%d%d", &a, &b, &c), add(a, b, -c);
    		if(t==2)	scanf("%d%d%d", &a, &b, &c), add(b, a, c);
    		if(t==3) 	scanf("%d%d", &a, &b), add(a, b, 0), add(b, a, 0);
    	}
    	for(int i=1; i<=n; i++)	add(n+1, i, -1); 		//加入一个指向所有点的超级结点,使图“联通”起来
    													//从n+1号点开始,只有负边才能启动松弛,所以,加入的边为-1 
    	flag=false;
    	dis[n+1]=0;
    	SPFA(n+1);
    	if(flag)	printf("No
    ");
    	else		printf("Yes
    ");
    	return 0;
    }
    
    洛谷P3275糖果DFS-SPFA版,在差分约束类问题中,建议使用DFS的SPFA
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    #include<algorithm>
    using namespace std;
    const int maxn=100010;
    int N, K, dis[maxn], flag, instack[maxn];
    struct edge{ int t, w; edge *nxt; edge(int to, int val, edge *next){ t=to, w=val, nxt=next; }};
    edge *h[maxn];
    void add(int u, int v, int w){ h[u]=new edge(v, w, h[u]); }
    
    void SPFA(int x)
    {
    	instack[x]=1;
    	for(edge *p=h[x]; p; p=p->nxt)
    	{
    		if(flag)	return;
    		if(dis[p->t]<dis[x]+p->w)						//求最长路 
    		{
    			if(instack[p->t])
    			{
    				flag=true;
    				return;
    			}
    			dis[p->t]=dis[x]+p->w;
    			SPFA(p->t);
    		}
    	}
    	instack[x]=0;
    }
    
    int main()
    {
    	scanf("%d%d", &N, &K);
    	for(int i=1, x, a, b; i<=K; i++)
    	{
    		scanf("%d%d%d", &x, &a, &b);
    		if(x==1)	add(b, a, 0), add(a, b, 0);
    		if(x==2)	add(a, b, 1);
    		if(x==3)	add(b, a, 0);
    		if(x==4)	add(b, a, 1);
    		if(x==5)	add(a, b, 0);
    	}
    	for(int i=N; i>=1; i--)	add(N+1, i, 1);				//加边的顺序很重要,会极大影响搜索顺序
    	SPFA(N+1);
    	if(flag)	printf("-1
    ");
    	else
    	{
    		long long ans=0;								//小心1~100000的数列和是超过int范围的 
    		for(int i=1; i<=N; i++)	ans+=dis[i];
    		printf("%lld
    ", ans);
    	}
    	return 0;
    }
    
    洛谷P3275糖果BFS-SPFA版,90分,TLE第6个点
    //朴素BFS的SPFA会TLE一个点,SLL和SLF优化是否能过没有尝试,本题使用DFS的SPFA效率最高 
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    #include<algorithm>
    using namespace std;
    const int maxn=100010;
    int N, K, dis[maxn], cnt[maxn], inq[maxn];
    struct edge{ int t, w; edge *nxt; edge(int to, int val, edge *next){ t=to, w=val, nxt=next; }};
    edge *h[maxn];
    void add(int u, int v, int w){ h[u]=new edge(v, w, h[u]); }
    
    bool SPFA(int x)
    {
    	queue<int> q;
    	q.push(x);
    	inq[x]=cnt[x]=1;
    	while(!q.empty())
    	{
    		int from=q.front();
    		q.pop();
    		inq[from]=0;
    		for(edge *p=h[from]; p; p=p->nxt)
    		{
    			int to=p->t, w=p->w;
    			if(dis[to]<dis[from]+w)						//求最长路
    			{
    				cnt[to]=cnt[from]+1;
    				if(cnt[to]>N+1)	return false;
    				dis[to]=dis[from]+w;
    				if(!inq[to])
    				{
    					q.push(to);
    					inq[to]=1;
    				}
    			}
    		}
    	}
    	return true;
    }
    
    int main()
    {
    	scanf("%d%d", &N, &K);
    	for(int i=1, x, a, b; i<=K; i++)
    	{
    		scanf("%d%d%d", &x, &a, &b);
    		if(x==1)	add(b, a, 0), add(a, b, 0);
    		if(x==2)	add(a, b, 1);
    		if(x==3)	add(b, a, 0);
    		if(x==4)	add(b, a, 1);
    		if(x==5)	add(a, b, 0);
    	}
    	for(int i=N; i>=1; i--)	add(N+1, i, 1);				//加边的顺序很重要,会极大影响搜索顺序 
    	if(!SPFA(N+1))	printf("-1
    ");
    	else
    	{
    		long long ans=0;								//小心1~100000的数列和是超过int范围的
    		for(int i=1; i<=N; i++)	ans+=dis[i];
    		printf("%lld
    ", ans);
    	}
    	return 0;
    }
    
  • 相关阅读:
    Pyhon基础过滤字符串中的字母数字特殊符号
    [编程题] 斐波那契数列
    左旋转字符串(Java)-循环Index方式
    [编程题]字符流中第一个不重复的字符
    6525. 【2020.4.1模拟】Valleys
    6515. 【GDOI2020模拟03.28】数一数(one)
    6516. 【GDOI2020模拟03.28】数二数(two)
    6508. 【GDOI2020模拟03.11】我的朋友们
    6494. 【GDOI2020模拟03.08】勘探
    6491. 【GDOI2020模拟03.04】铺路
  • 原文地址:https://www.cnblogs.com/lfyzoi/p/10669511.html
Copyright © 2011-2022 走看看