学习差分约束有感//2018/7/2

去看看我的阶段性总结,就会发现这些东西都是n年前学的  传送门。

敲了n道题,终于对差分约束系统有了一些门道,发现他也没有我想象的那么难(虽然还是没有搞懂他与差分到底有什么关系)。

好吧,其实如同前几篇的感想一般,也有可能比较水

最近做题多了,发现差分约束都一个套路与图论spfa的关系也是分不开的

queue <int> q;//定义一个名为q的优先队列
q.push(x);//将x放入队尾
q.back();//访问队列中的最后一个元素
q.pop();//删除队列中的
q.front();//方位队列中的第一个元素
q.empty();//差点把他忘了,判断队列是否为空

有了这么一个小工具,就不用去捣鼓什么head,tail指针了,灰常方便,spfa与优先队列更配呦。

下面我们再一次回归正题

差分约束的题,有一个十分重要的东西,就是超级源点,还要注意超级远点的边与权值是根据题目的改变而改变的,而且起搭建的顺序(顺序,倒序)对时间复杂度也是有一定影响的(我也不知道为什么),据我所知,好像是倒序快那么一点点,因为我记得luogu上有一道题就是会专门卡掉你顺序的建边的。

明确了这一点之后,再来讲述一波差分约束的基本形式在差分约束的题目中会有n个形似a-b>c的不等式,就是改变一下不等号的方向与加不加等号的问题,而我们的工作就是吧这n个不等式化为同一种形式(不加等号的那种)等号就把不等号右边的书-1(+1)然后去掉等号(因为问题都是在整数范围内的),然后具体问题具体分析,比如说求最大的问题就转化为spfa求最大生成树,最短路问题就转化为spfa求最小生成树(然而笔者并没有试过其他算法行不行),还有就是求两点之间的最大最小距离的,那么只要不把最后的dis数组累加就行了,(附加练习题【poj 3169 layout】笔者太懒,懒得贴链接(其实就是找不到了),前面的超链接是他自己的加的,和我没关系)。额,好像扯远了。顺便提一句,储存要用邻接表(废话,都说了要用spfa)

而求出了最短路之后问题一般就迎刃而解了,所以差分约束的题一般就一眼可以看出来。

什么,你说要题目的标程??

好吧好吧(请忽略奇奇怪怪的注释(笔者比较懒,懒得删))

#include <iostream>
#include <cstdio>
#include <queue>
#include <stdlib.h>
#include <memory.h>
//#pragma GCC optimize(2)

using namespace std;

#define C getchar()  
#define maxn 1010

inline int read()
{
    int x=0;char ch;bool flag=true;
    for(;!isdigit(ch);ch=C)
    if(ch=='-')  flag=false;
    for(;isdigit(ch);ch=C)
    x=(x<<3)+(x<<1)+(ch^48);
    return flag?x:-x;  
}

struct biao
{
    int next,to,value;
}e[20010]={};
int linkk[maxn]={};
int len;
bool vis[maxn]={};
int n,x,y;

void insert(int x,int y,int value)
{
    e[++len].to=y;
    e[len].value=value;
    e[len].next=linkk[x];
    linkk[x]=len;
}

//void rec(int x)
//{
//    for(int i=1;i<=n;i++)
//    {
//        if(dis[x][i]==1&&!vis[i])
//        {
//            vis[i]=1;
//            rec(i);
//        }
//    }
//}

queue <int> q;
int que[maxn]={};
int dist[maxn]={};

void spfa(int i)
{
	memset(vis,0,sizeof(vis));
	memset(que,0,sizeof(que));
	memset(dist,10,sizeof(dist));//难道宏定义又翻车了?? 
	vis[i]=1;
	dist[i]=0;
	que[i]++;
    q.push(i);
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        vis[u]=0;
        que[u]++;
        for(int i=linkk[u];i;i=e[i].next)
           {
           int t=e[i].to;
		   if(dist[t]>dist[u]+e[i].value)
           {
               dist[t]=dist[u]+e[i].value;
               que[t]++;
               if(que[u]==n)
        		{
            	printf("%d",-1);
            	exit(0);
        		}
//               cout<<"haha"<<' '<<e[i].value<<endl;
               if(!vis[t])   q.push(t);
           }}
    }
}

int main()
{
//    freopen("layout.in","r",stdin);
//    freopen("layout.out","w",stdout);
    n=read();
    x=read();
    y=read();
    for(int i=1;i<=x;i++)
    {
        int x1=read(),y1=read(),value=read();
        insert(x1,y1,value);
    }
    for(int i=1;i<=y;i++)
    {
        int x1=read(),y1=read(),value=read();
        insert(y1,x1,-value);
    }
//    rec(1);
//    if(vis[n]==0)
//    {
//        printf("%d",-2);
//        return 0;
//    }
    for(int i=1;i<=n;i++)//记得luogu上的有个题目就卡这个 
      insert(0,i,0);// 感觉所有的差分约束题都同一个套路 
//    memset(vis,0,sizeof(vis));
//	int ans=0;
    spfa(0); 
    spfa(1);
   	if(dist[n]==168430090)
   	  printf("%d",-2);
   	else
   	   printf("%d",dist[n]);
//    printf("%d",ans);
    return 0;
}//万年审题不清的玩家就是我 


题目大意

有好多只奶牛(竟然不是farmer john的)提出了好多个要求,好基友们的距离不可以超过n,而坏基友的距离不可以小于n,求1与n号奶牛最远可以是多少(无解出-1,无限出-2)

值得一提的是在这题的另一种做法上spfa不是void函数而是bool函数,非常巧妙地判断了-1的情况(然而笔者这么做就TLE了)

那么既然已经讲完了基本性质,就来分析一下这篇具体代码吧,在这一片代码中(第一个注释是o2优化,比赛的时候不要用,不然会和爆炸oj一个下场),首先把涞流们的奇奇怪怪的要求通过数学方法转换成a-b>c的形式,再通过邻接表储存就出现了一个图,

再通过炒鸡源点将图变为一个连通图,然后就可以通过spaf解决这个问题了

不过好像很多差分约束的题都口以用带权值的并查集做,比如说爆炸oj1202:狡猾的商人

luogu传送门                                    爆炸oj传送门

(逃……

转载请标明出处(我相信不会有人转载的)

点赞