首页 > 代码库 > 培训补坑(day5:最小生成树+负环判断+差分约束)

培训补坑(day5:最小生成树+负环判断+差分约束)

补坑补坑((╯‵□′)╯︵┻━┻

内容真的多。。。

一个一个来吧。

首先是最小生成树。

先讲一下生成树的定义

生成树就是在一张图上选取一些边,使得整个图上所有的点都连通。

那么我们要求的最小生成树有两种算法可以求:1、prim算法,2、kruskal算法

我们先讲讲prim算法

prim算法有点像最短路中的dijstra,操作都几乎一样,原理就是从所有在队列中的点出发,找到最小的一条边,并把它连起来,这样子能够保证局部最优性。

在此我不讲这种算法,不过有兴趣的人可以去学一学。

我重点推出的是kruskal算法:原因就是:又短,又好写,而且一点都不难理解。

首先我们先把所有的边都排个序,然后我们从小到大枚举所有的边,如果一条边两端的点不在同一个集合内,那么我们就链接这一条边,并且合并集合(合并集合的操作可以用并查集快速维护)

下面附上代码

 

#include<cstdio>
#include<algorithm>
#define MN 5005
#define M 200005
using namespace std;
int n,m,cnt=0,ans=0;
int f[MN];
struct edge{
    int u,v,w;
}g[M];
bool cmp(edge a,edge b){return a.w<b.w;}
int getf(int x){return !f[x]?x:f[x]=getf(f[x]);}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)scanf("%d%d%d",&g[i].u,&g[i].v,&g[i].w);
    sort(g+1,g+m+1,cmp);
    for(int i=1;i<=m&&cnt<n-1;i++){
        int x=getf(g[i].u),y=getf(g[i].v);
        if(x!=y){
            cnt++;
            ans+=g[i].w;
            f[x]=y;
        }
    }
    if(cnt==n-1)printf("%d\n",ans);else puts("orz");
}

如果对于这两种算法不懂的同学们可以去这篇博客上看一看它的图解,我就不献丑啦QAQ(其实是内容太多,比较懒QAQ)

——————————————————我是分割线———————————————————

下面讲一讲负环判断:

我们在上次讲过一次最短路,而我在那一次的讲解中提到过,dij算法是不能在有负环的图中跑的。

这也就意味着,spfa可以。

对于每次spfa中,如果我们不使用循环队列,那么我们的队列元素开的大小就是n*n

这是因为在spfa中,一个点最多入队n次。可是如果我们想,spfa中出现负环会怎么样呢?

显然,环内的点会重复入队,然后T掉。

但这样也说明,如果一个点入队次数超过n次,那么一定有负环出现。

这也就是我们判断负环的方法。

在spfa中我们开一个cnt数组记录每个点入队的次数,如果次数超过n次,那么就存在负环。

由于太过简单,代码我就不贴了QAQ

——————————————————我是分割线———————————————————

最后是差分约束。

差分约束的意思就是说,在一道题中有很多的变量,变量之间满足很多的不等式,然后要我们求解的一系列问题。

我们先来看一看例题。

——————————————————我是分割线———————————————————

题目:

  当排队等候喂食时,奶牛喜欢和它们的朋友站得靠近些。FJ有N(2<=N<=1000)头奶牛,编号从1到N,沿一条直线站着等候喂食。奶牛排在队伍中的顺序和它们的编号是相同的。因为奶牛相当苗条,所以可能有两头或者更多奶牛站在同一位置上。即使说,如果我们想象奶牛是站在一条数轴上的话,允许有两头或更多奶牛拥有相同的横坐标。 
    一些奶牛相互间存有好感,它们希望两者之间的距离不超过一个给定的数L。另一方面,一些奶牛相互间非常反感,它们希望两者间的距离不小于一个给定的数D。给出ML条关于两头奶牛间有好感的描述,再给出MD条关于两头奶牛间存有反感的描述。(1<=ML,MD<=10000,1<=L,D<=1000000) 
    你的工作是:如果不存在满足要求的方案,输出-1;如果1号奶牛和N号奶牛间的距离可以任意大,输出-2;否则,计算出在满足所有要求的情况下,1号奶牛和N号奶牛间可能的最大距离。

我们先来看一看一个等式,比如题目中要求的第一个等式:d[j]-d[i]<=L,这个等式有没有感觉有点熟悉?细心的人就会发现当d[j]-d[i]>L的时候就要更新答案,这不就是最短路吗!!

是的,这就是最短路,于是我们就将一个数论问题变成了一个图论问题。

所以我们只需要把所有不等式转换为d[i]-d[j]<=L的不等式,然后我们从j到i连一条L的边

然后我们对着这个图直接跑最短路就好了(是不是很棒!)

那么我们又要怎么判断-1呢?这就是我们上面讲的负环判断的应用了。因为如果出现负环,很显然就无法满足条件,那么就是无解情况了。

下面讲两个建图的容易错误的地方;

一个是有时候题目会要求不能有2个人同时站在同一位置上。所以我们有时候需要满足d[i+1]-d[i]>=1不等式

还有就是如果要求A和B相同,那么我们要在AB之间连上双向边。

那么今天的总结就到这里啦。

附上这道例题的代码。

#include<cstdio>
#include<cstring>
#define MN 30005
#define inf 0x3f3f3f3f
#define M 1005
using namespace std;
inline int read() 
{ 
    int x=0;char ch=getchar(); 
    while(ch<0||ch>9){ch=getchar();} 
    while(ch>=0&&ch<=9){x=(x<<3)+(x<<1)+ch-0;ch=getchar();} 
    return x; 
}
int head[M],cnt[M],dis[M],que[M<<1];
int n,ml,md,num,t;
bool visit[M];
struct edge{
    int to,next,val;
}g[MN];
void ins(int u,int v,int we){g[++num].next=head[u];head[u]=num;g[num].to=v;g[num].val=we;}
void insw(int u,int v,int we){ins(u,v,we);ins(v,u,we);}
bool spfa(){
    for(int i=1;i<=n;i++)dis[i]=inf;
    memset(visit,0,sizeof(visit));
    int h=1,tail=1;que[h]=1;dis[1]=0;
    while(h<=tail){
        int tmp=que[h++];
        for(int i=head[tmp];i;i=g[i].next)
            if(dis[g[i].to]>dis[tmp]+g[i].val){
            dis[g[i].to]=dis[tmp]+g[i].val;cnt[g[i].to]++;
                if(!visit[g[i].to]){
                    visit[g[i].to]=1;
                    if(cnt[g[i].to]>n)return true;
                    if(dis[g[i].to]<dis[que[h]])que[--h]=g[i].to;
                    else que[++tail]=g[i].to;
                }   
            }   
        visit[tmp]=0;
    }
    return false;
}
int main(){
    scanf("%d%d%d",&n,&ml,&md);
    int s,t,value;
    for(int i=2;i<=n;i++)ins(i,i-1,0);
    for(int i=1;i<=ml;i++)scanf("%d%d%d",&s,&t,&value),ins(s,t,value);
    for(int i=1;i<=md;i++)scanf("%d%d%d",&s,&t,&value),ins(t,s,-value);
    if(spfa())printf("-1\n");
    else if(dis[n]==inf)printf("-2\n");
    else printf("%d\n",dis[n]);
}

 

培训补坑(day5:最小生成树+负环判断+差分约束)