图论复习(各类习题)

可以结合这篇博客进行复习:http://www.cnblogs.com/z360/p/7363034.html

       一、强连通分量、缩点

习题:

洛谷——P2746 [USACO5.3]校园网Network of Schools

传送门

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 1010 
using namespace std;
bool vis[N];
int n,m,s1,s2,tot,tim,sum,top,ans1,ans2;
int in[N],out[N],low[N],dfn[N],head[N],stack[N],belong[N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
struct Edge
{
    int to,next;
}edge[N];
int add(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
int tarjan(int x)
{
    dfn[x]=low[x]=++tim;
    stack[++top]=x,vis[x]=true;
    for(int i=head[x];i;i=edge[i].next)
    {
        int t=edge[i].to;
        if(vis[t]) low[x]=min(low[x],dfn[t]);
        else if(!dfn[t]) tarjan(t),low[x]=min(low[x],low[t]);
    }
    if(low[x]==dfn[x])
    {
        sum++;belong[x]=sum;
        for(;stack[top]!=x;top--)
        {
            vis[stack[top]]=false;
            belong[stack[top]]=sum;
        }
        top--,vis[x]=false;
    }
}
int shink_point()
{
    for(int i=1;i<=n;i++)
     for(int j=head[i];j;j=edge[j].next)
     {
         int t=edge[j].to;
         if(belong[i]!=belong[t])
          in[belong[t]]++,out[belong[i]]++;
     }
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        while(1)
        {
            m=read();
            if(m==0) break;
            add(i,m);
        }
    }
    for(int i=1;i<=n;i++)
     if(!dfn[i]) tarjan(i);
    shink_point();
    for(int i=1;i<=sum;i++)
    {
        if(in[i]==0) s1++;
        if(out[i]==0) s2++;
     } 
    if(sum==1) ans2=0;
    else ans2=max(s1,s2);
    ans1=s1;
    printf("%d\n%d",ans1,ans2);
    return 0;
}
tarjan求强连通分量

小总结:1.最少让几个人知道就可以做到让所有的人都知道信息,最少知道的人的数目即为缩完点后入读为零的点的个数

    2.最少加入几条边就可以使这个图变成一个强连通图,加的边的条数即为缩完点后Max(入读为零的点的个数,出度为零的点的个数)

poj——3177 Redundant Paths

传送门

求一个无向图在加入多少条边后变成边双连通图。跟上面的类型差不多相同,只不过是一个为有向图,一个为无向图。既然类型相同,那么我们采用相同的思想来做这道题,首先我们先tarjan缩点,然后统计入读为0点的点的个数。有同学肯定要问了,无向图啊,怎么可能入读为0呢,而且你怎么知道是入读还是初读的啊,并且tarjan缩点的前提不是有向图吗?这是无向图啊,怎么缩点?       这就要归功于:if(i==(1^pre)) continue; 对,他是用两条边,但是我们这个地方只让他走一条边,这样不就和有向图缩点一样了吗?!不知道是入读还是出度,那么直接统计度数为2的点的个数。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 5005
using namespace std;
bool vis[N];
long long  n,m,x,y,ans,tot=1,tim,sum,top;
long long du[N],dfn[N],low[N],stack[N],belong[N];
long long head[20010];
int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
    while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
struct Edge
{
    int from,next,to;
}edge[20010];
void add(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
int tarjan(int now,int pre)
{
    dfn[now]=low[now]=++tim;
    stack[++top]=now; 
    for(int i=head[now];i;i=edge[i].next)
    {
        int t=edge[i].to;
        if(i==(1^pre)) continue;
        if(!dfn[t]) tarjan(t,i),low[now]=min(low[now],low[t]);
        else low[now]=min(low[now],dfn[t]);
    }
    if(low[now]==dfn[now])
    {
        sum++; belong[now]=sum;
        for(;stack[top]!=now;top--)
          belong[stack[top]]=sum;
        top--;
    }
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
     x=read(),y=read(),add(x,y),add(y,x);
    tarjan(1,0);
    for(int i=1;i<=n;i++)
     for(int j=head[i];j;j=edge[j].next)
      if(belong[i]!=belong[edge[j].to]) du[belong[i]]++,du[belong[edge[j].to]]++;
    for(int i=1;i<=n;i++)
     if(du[i]==2) ans++;
    printf("%d",(ans+1)>>1);
    return 0;
}
无向图缩点

洛谷——P2661 信息传递 && cogs619. [金陵中学2007] 传话

传送门 

信息传递tarjan判环
传话:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 10010
using namespace std;
bool vis[N];
int n,m,x,y,tot,tim,sum,top;
int s[N],dfn[N],low[N],head[N],stack[N],belong[N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
struct Edge
{
    int to,next;
}edge[N];
int add(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
int tarjan(int x)
{
    dfn[x]=low[x]=++tim;
    stack[++top]=x;vis[x]=true;
    for(int i=head[x];i;i=edge[i].next)
    {
        int t=edge[i].to;
        if(vis[t]) low[x]=min(low[x],dfn[t]);
        else if(!dfn[t]) tarjan(t),low[x]=min(low[x],low[t]);
    }
    if(low[x]==dfn[x])
    {
        sum++;belong[x]=sum,s[sum]++;
        for(;stack[top]!=x;top--)
        {
            s[sum]++;
            vis[stack[top]]=false;
            belong[stack[top]]=sum;
        }
        vis[x]=false;top--;
    }
}
int main()
{
    freopen("messagez.in","r",stdin);
    freopen("messagez.out","w",stdout);
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        x=read(),y=read();
        add(x,y);
    }
    for(int i=1;i<=n;i++)
     if(!dfn[i]) tarjan(i);
    for(int i=1;i<=n;i++)
     if(s[belong[i]]==1) printf("F\n");
     else printf("T\n");
    return 0;
}
tarjan判环

tarjan判环,可以用来解决以下问题   1.一个人发出的信息是否能传给自己  2.一群人玩游戏,如果自己的信息被别人告诉那么游戏结束,问最少进行的轮数,用tarjan求最小环中的点的个数

洛谷—— P3469 [POI2008]BLO-Blockade

传送门

题解:我们知道割掉一个点后,能够对整张图的不连通有序对造成影响的一定为割点,因此,我们用tarjan处理,我们将这个图看成一颗树,将点割掉以后不连通的有序对为这个点子树内的点的·个数*这个点父亲里面的点的个数。这样我们又能得知他们之间不能相连的点数就是他的父亲节点内的个数*塔子树节点内的个数、。即ans【i】=t*(n-t-1) t=size[i] 这样我们求出了从该点外不能互相到达的对数。最后再加上由于这个点割去了,因此这个点不能到达所有的点。最后乘2

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 500010
#define ll long long
using namespace std;
ll ans[N];
int n,m,x,y,tot,tim;
int dfn[N],low[N],head[N],size[N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
struct Edge
{
    int to,next;
}edge[N<<1];
int add(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
int tarjan(int x)
{
    int z=0;size[x]=1;
    dfn[x]=low[x]=++tim;
    for(int i=head[x];i;i=edge[i].next)
    {
        int t=edge[i].to;
        if(!dfn[t])
        {
            tarjan(t);size[x]+=size[t];
            low[x]=min(low[x],low[t]);
            if(dfn[x]<=low[t])
            {
                ans[x]+=(ll)z*size[t];
                z+=size[t];
            }
        }
        else low[x]=min(low[x],dfn[t]);
    }
    ans[x]+=(ll)z*(n-z-1);
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        x=read(),y=read();
        add(x,y),add(y,x);
    }
    tarjan(1);
    for(int i=1;i<=n;i++)
     printf("%lld\n",(ans[i]+n-1)<<1);
    return 0;
}
割掉一个点后,不连通的对数

 

最受欢迎的牛类的类的题目,我们需要tarjan缩点然后初度为零的点即为最受欢迎的牛

              二、割边、割点

习题:

洛谷——P3225 [HNOI2012]矿场搭建

传送门

题解:我们要求最少修建几个逃生处才能使任意一个地方炸了所有的人都能够逃生。我们假设将割点炸掉以后,这个图中一定会变成好几个双连通分量,我们可以知道如果一个双连通分量中没有割点,那么我们需要建两个逃生处扎了一个还能去另一个;如果连接一个割点,我们只需要在双连通图中建一个就好了,炸了割点还能逃生;如果链接两个,那个无论炸那个我们都能通过另一个逃生

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 510
using namespace std;
long long ans2;
bool vis[N],cut_point[N],cut_edge[N];
int head[N<<1],dfn[N],low[N],belong[N];
int n,m,x,y,s,tim,cut,tot,sum,cnt,ans1;
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*10+ch-'0',ch=getchar();
    return x*f;
}
struct Edge
{
    int to,next;
}edge[N<<1];
int add(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
int tarjan(int x,int pre)
{
    int sum=0;bool boo=false;
    dfn[x]=low[x]=++tim;
    for(int i=head[x];i;i=edge[i].next)
    {
        int t=edge[i].to;
        if((1^i)==pre) continue;
        if(!dfn[t])
        {
            sum++;tarjan(t,i);
            low[x]=min(low[x],low[t]);
            if(low[t]>dfn[x]) cut_edge[i/2]=true;
            if(low[t]>=dfn[x]) boo=true;    
        }
        else low[x]=min(low[x],dfn[t]);    
    }    
    if(pre==-1){if(sum>1) cut_point[x]=true;}
    else if(boo) cut_point[x]=true;
}
int begin()
{
    n=tim=ans1=0;tot=ans2=1;
    for(int i=1;i<=N;i++)
    {
        vis[i]=cut_point[i]=0;
        dfn[i]=low[i]=belong[i]=0;
     } 
    memset(head,0,sizeof(head));
    memset(edge,0,sizeof(edge));
}
int dfs(int x)
{
    s++;vis[x]=true;belong[x]=sum;
    for(int i=head[x];i;i=edge[i].next)
    {
        int t=edge[i].to;
        if(cut_point[t]&&belong[t]!=sum)  cut++,belong[t]=sum;
        if(!vis[t]&&!cut_point[t]) dfs(t);
    }
}
int main()
{
    while(1)
    {
        m=read();
        if(m==0) break;begin();
        for(int i=1;i<=m;i++)
        {
            x=read(),y=read();
            add(x,y),add(y,x);
            n=max(n,max(x,y));
        }
        for(int i=1;i<=n;i++)
         if(!dfn[i]) tarjan(i,-1);
        sum=0;
        for(int i=1;i<=n;i++)
        {
            if(vis[i]||cut_point[i]) continue;
            s=cut=0,sum++;dfs(i);
            if(!cut) ans1+=2,ans2*=(long long)(s*(s-1))>>1;
            if(cut==1) ans1++,ans2*=(long long)s;
        }
        printf("Case %d: %d %lld\n",++cnt,ans1,ans2);
    }
    return 0;
}
tarjan+dfs

当损坏一个点或一条边就不能是图联通的时候,就要求割点割边

POJ——SPF

传送门

求删除这个割点后有几个强连通子图

解决这一类问题的时候我们可以先求出割点,我们有知道low值相同的点在一个强连通分量里,然后我们可以求与该割点相连的点的low值不同的个数

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 10010
using namespace std;
int n,x,y,tot,tim,cnt,sum;
int dfn[N],low[N],head[N];
bool flag,vis[N],cut_point[N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
struct Edge
{
    int from,next,to;
}edge[N*200];
void add(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
int begin()
{
    tim=0,tot=1;flag=false;
    memset(head,0,sizeof(head));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(cut_point,0,sizeof(cut_point));    
}
int tarjan(int x,int pre)
{
    int s=0; bool boo=false;
    dfn[x]=low[x]=++tim;
    for(int i=head[x];i;i=edge[i].next)
    {
        if((i^1)==pre) continue;
        int t=edge[i].to;
        if(!dfn[t])
        {
            s++,tarjan(t,i);
            low[x]=min(low[t],low[x]);
            if(dfn[x]<=low[t]) boo=true;
        }
        else low[x]=min(low[x],dfn[t]);
    }
    if(pre==-1){if(s>1) cut_point[x]=true;}
    else if(boo) cut_point[x]=true;
}
int main()
{
    while(1)
    {
        x=read();if(x==0) break;
        begin();y=read(),add(x,y),add(y,x);
        n=max(n,max(x,y)),cnt++;
        while(1)
        {
            x=read();
            if(x==0) break;
            y=read(),add(x,y),add(y,x);
            n=max(n,max(x,y));
        }
        printf("Network #%d\n",cnt);
        tarjan(1,-1);
        for(int i=1;i<=n;i++)
         if(cut_point[i])
         {
             sum=0;flag=true;
            memset(vis,0,sizeof(vis));
             for(int j=head[i];j;j=edge[j].next)
             {
                 int t=edge[j].to;
                 if(vis[low[t]]) continue;
                 vis[low[t]]=true,sum++;
            }
            printf("  SPF node %d leaves %d subnets\n",i,sum);
         }
        if(!flag) printf("  No SPF nodes\n");
        printf("\n");
    }
    return 0;
}
删除割点后联通子图的个数

 

 

              三、最小生成树

T3 BZOJ——1821: [JSOI2010]Group 部落划分 Group

题解:要将这n各部落全部连接起来需要n-1条边,我们要将它划分成m个部落,也就是说我们要有m个部落不能被连接起来,我们要先将距离小的两个部落先连起来,这就恰好是最小生成树的思想,所以要想到最小生成树也不难。我们将这m个部落连起来恰好是用m-1条边,也就是说我们将除了那m个部落连起来,恰好是要用n-1-(m-1)也就是n-m条边,然后这m部落间的最小的距离就是n-m+1条边的长度了

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 1010
using namespace std;
double ans;
int n,k,x,y,s,fx,fy,sum,fa[N],xx[N],yy[N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
struct Edge
{
    int x,y;
    double z;
}edge[N*N];
int cmp(Edge a,Edge b)
{return a.z<b.z;}
int find(int x)
{
    if(x==fa[x]) return  x;
    fa[x]=find(fa[x]);
    return fa[x];
}
int main()
{
    n=read(),k=read();
    for(int i=1;i<=n;i++) xx[i]=read(),yy[i]=read();
    for(int i=1;i<=n;i++)
     for(int j=1;j<=n;j++)
      if(i!=j)
      {
          ++s;
          edge[s].x=i;
          edge[s].y=j;
          edge[s].z=sqrt((double)pow(xx[i]-xx[j],2)+(double)pow(yy[i]-yy[j],2));
      }
    for(int i=1;i<=n;i++) fa[i]=i;
    sort(edge+1,edge+1+s,cmp);
    for(int i=1;i<=s;i++)
    {
        x=edge[i].x,y=edge[i].y;
        fx=find(x),fy=find(y);
        if(fa[fx]==fy) continue;
        fa[fx]=fy;sum++;
        if(sum==n-k+1) {ans=edge[i].z;break;}
    }
    printf("%.2lf",ans);
    return 0;
}
求将n个部落划分成m个,使这些部落间的最短距离最大,求这m部落间最短距离

cogs——2419. [HZOI 2016]公路修建2

 题解:现选k种A道路,然后在跑最小生成树,求最小生成树最长边

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 210000
using namespace std;
int n,m,x,y,k,z1,z2,fx,fy,sum,ans,fa[N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
struct Edge
{
    int x,y,z1,z2;
}edge[N];
int cmp1(Edge a,Edge b)
{return a.z1<b.z1;}
int cmp2(Edge a,Edge b)
{return a.z2<b.z2;}
int find(int x)
{
    if(x==fa[x]) return  x;
    fa[x]=find(fa[x]);
    return fa[x];
}
int main()
{
    freopen("hzoi_road2.in","r",stdin);
    freopen("hzoi_road2.out","w",stdout);
    n=read(),k=read(),m=read();
    for(int i=1;i<m;i++)
    {
        x=read(),y=read(),z1=read(),z2=read();
        edge[i].x=x;
        edge[i].y=y;
        edge[i].z1=z1;
        edge[i].z2=z2;
    }
    for(int i=1;i<=n;i++) fa[i]=i;
    sort(edge+1,edge+m+1,cmp1);
    for(int i=1;i<=m;i++)
    {
        if(k==0) break;
        x=edge[i].x,y=edge[i].y;
        fx=find(x),fy=find(y);
        fa[fx]=fy;sum++;
        ans=max(ans,edge[i].z1);
        if(sum==k)break;
    }
    sort(edge+1,edge+1+m,cmp2);
    for(int i=1;i<=m;i++)
    {
        x=edge[i].x,y=edge[i].y;
        fx=find(x),fy=find(y);
        if(fx==fy) continue;
        fa[fx]=fy;
        ans=max(ans,edge[i].z2);
    }
    printf("%d",ans);
    return 0;
}
在n个村庄间有两种道路,求至少用k种A道路的使所有道路连通的最短距离中的最长路径

HDU——3072 Intelligence System

题解:看到这个题首先想到的便是tarjan缩点然后求最小生成树吧,但是我们会发现wa掉,为什么,因为我们这个地方是有向图,如果采用最小生成树使他们在一个并查集里,但是事实上这几个点可能不连通,所以我们用贪心的思想,求出于每个点联通的最短路的长度,然后累加’

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 300010
#define maxn 0x7fffffff
using namespace std;
bool vis[N];
long long ans;
int n,m,x,y,z,s,tot,num,sum,top,tim;
int fa[N],low[N],dfn[N],cost[N],stack[N],head[N],belong[N];
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*10+ch-'0'; ch=getchar();}
    return x*f;
}
struct Edge
{
    int to,dis,next;
}edge[N];
struct Edde
{
    int to,dis,next;
}edde[N];
int add(int x,int y,int z)
{
    tot++;
    edge[tot].to=y;
    edge[tot].dis=z;
    edge[tot].next=head[x];
    head[x]=tot;
}
void begin()
{
    s=0,tim=0,sum=0,tot=0;
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(vis,0,sizeof(vis));
    memset(head,0,sizeof(head));
    memset(belong,0,sizeof(belong));
}
void tarjan(int now)
{
    dfn[now]=low[now]=++tim;
    stack[++top]=now; vis[now]=true;
    for(int i=head[now];i;i=edge[i].next)
    {
        int t=edge[i].to;
        if(vis[t]) low[now]=min(low[now],dfn[t]);
        else if(!dfn[t]) tarjan(t),low[now]=min(low[now],low[t]);
    }
    if(low[now]==dfn[now])
    {
        sum++;belong[now]=sum;
        for(;stack[top]!=now;top--)
        {
            int x=stack[top];
            belong[x]=sum;vis[x]=false;
        }
        vis[now]=false;top--;
    }
}
void work()
{
    ans=0;
    for(int i=1;i<=n;i++)
      for(int j=head[i];j;j=edge[j].next)
      {
          int t=edge[j].to;
          if(belong[i]!=belong[t])
            cost[belong[t]]=min(cost[belong[t]],edge[j].dis);    
     } 
   for(int i=1;i<=sum;i++)
    if(cost[i]!=maxn) ans+=(long long)cost[i];
   printf("%lld\n",ans);
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        begin();
        for(int i=1;i<=n;i++) cost[i]=maxn;
        for(int i=1;i<=m;i++)
          x=read(),y=read(),z=read(),add(x+1,y+1,z);
        for(int i=1;i<=n;i++)
         if(!dfn[i]) tarjan(i);
        work();
    }
    return 0;
}
求将n个点连通的最短路的长度,如果存在几个点在环中,那么这几个点连通不需要花费

洛谷——P2330 [SCOI2005]繁忙的都市

题解:最少的道路条数即为最小生成树需要加的边的条数即为n-1。第二问为求最小生成树中的最长边

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 50010
using namespace std;
int n,m,x,y,z,fx,fy,sum,fa[N],ans1,ans2;
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*10+ch-'0',ch=getchar();
    return x*f;
}
struct Edge
{
    int x,y,z;
}edge[N<<1];
int cmp(Edge a,Edge b)
{return a.z<b.z;}
int find(int x)
{
    if(x==fa[x]) return x;
    fa[x]=find(fa[x]);
    return fa[x];
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        edge[i].x=read();
        edge[i].y=read();
        edge[i].z=read();
    }
    sort(edge+1,edge+1+m,cmp);
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        x=edge[i].x,y=edge[i].y;
        fx=find(x),fy=find(y);
        if(fx==fy) continue;
        fa[fx]=fy;sum++;
        ans2=max(ans2,edge[i].z);
        if(sum==n-1) break;
    }ans1=n-1;
    printf("%d %d",ans1,ans2);
    return 0;
}
将n个点连通,最少的道路的条数,分值最大的那条道路的分值

 

         四、最短路

1.最短路模板题

洛谷——P2047 社交网络

Floyd预处理从s到t最短路的条数,当两个点之间有边相连的时候我们它的最短路得条数初始化为1,然后我们跑Floyd,当从s到t的最短路需要由k来更新的时候,那么从s到t的最短路的条数即为从s到k最短路的条数*从k到t最短路的·条数,dis[s][k]+dis[k][t]=dis[s][t],那么最短路的条数就要在加上从s到k最短路的条数*从k到t最短路的·条数

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 210
#define maxn 999999
using namespace std;
double ans[N],f[N][N];
int n,m,x,y,z,dis[N][N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=n;i++)
     for(int j=1;j<=n;j++)
      dis[i][j]=(i!=j)*maxn;
    for(int i=1;i<=m;i++)
    {
        x=read(),y=read(),z=read();
        f[x][y]=f[y][x]=1;
        dis[x][y]=dis[y][x]=z;
    }
    for(int k=1;k<=n;k++)
     for(int i=1;i<=n;i++)
      for(int j=1;j<=n;j++)
       if(dis[i][j]>dis[i][k]+dis[k][j])
       {
           f[i][j]=f[i][k]*f[k][j];
           dis[i][j]=dis[i][k]+dis[k][j];
       }
       else 
        if(dis[i][j]==dis[i][k]+dis[k][j])
         f[i][j]+=f[i][k]*f[k][j];
    for(int k=1;k<=n;k++)
     for(int i=1;i<=n;i++)
      for(int j=1;j<=n;j++)
       if(i!=k&&k!=j&&dis[i][j]==dis[i][k]+dis[k][j]&&f[i][j])
        ans[k]+=(double)f[i][k]*f[k][j]/f[i][j];
    for(int i=1;i<=n;i++)
     printf("%.3lf\n",ans[i]);
    return 0;
}
求从s到t的最短路的条数及从求经过一个点的最短路的条数

洛谷——P2966 [USACO09DEC]牛收费路径Cow Toll Paths

题意:点有点权,边有边权,从一个点到另一个点的费用,是经过的所有道路的过路费之和,加上经过的所有的城市(包括起点和终点)的过路费的最大值,给定k个询问,求从s到t的最大值,如不连通输出-1

题解:Floyd,将点权进行排序,然后跑Floyd,我们在依次跑Floyd的时候保证k=n是的k的点权最大,也就是说这里我们在更新i到j这条路径的时候依次是使的中间节点的点权最大,这样保证中间节点的点权最大,这样就不用找每条路径上的最大点权了

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#define N 300
#define maxn 9999999
using namespace std;
int n,m,x,y,z,p,dy[N],dis[N][N],f[N][N];
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*10+ch-'0'; ch=getchar();}
    return x*f;
}
struct Node
{
    int c,num;
}node[N];
int cmp(Node a,Node b)
{return a.c<b.c;}
int main()
{
    n=read(),m=read(),p=read();
    for(int i=1;i<=n;i++) node[i].c=read(),node[i].num=i;
    sort(node+1,node+1+n,cmp);
    for(int i=1;i<=n;i++) dy[node[i].num]=i;
    for(int i=1;i<=n;i++)
     for(int j=1;j<=n;j++)
      f[i][j]=dis[i][j]=maxn;
    for(int i=1;i<=n;i++) f[i][i]=0;
    for(int i=1;i<=m;i++)
    {
        x=read(),y=read(),z=read();
        x=dy[x],y=dy[y];
        f[x][y]=f[y][x]=min(f[x][y],z);
    }
    for(int k=1;k<=n;k++)
     for(int i=1;i<=n;i++)
      for(int j=1;j<=n;j++)
      {
          f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
        dis[i][j]=min(dis[i][j],f[i][j]+max(max(node[i].c,node[k].c),node[j].c)); 
      }
    while(p--)
    {
        x=read(),y=read();
        x=dy[x],y=dy[y];
        if(dis[x][y]>=maxn) printf("-1\n");
        else printf("%d\n",dis[x][y]);
    }
    return 0;
}
求从一个点到另一个点的最短路的,定义为边权+最大点权

洛谷——P1629 邮递员送信

题解:从1点到每个点的最短路很好求,那么从每个点到1点的最短路的长度呢?我们建反向边,那么以前可以反向到达1点的点我们都可以从1点到达,那么我们在跑一边spfa,求出从1点到其他点的最短路即为从其他点到1点的最短路的长度。

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 300010
#define maxn 999999
using namespace std;
queue<int>q;
bool vis[N];
long long ans;
int n,m,x,y,z,tot,tot1;
int dis[N],dis1[N],head[N],head1[N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
struct Edge
{
    int to,dis,next;
}edge[N],edge1[N];
int add(int x,int y,int z)
{
    tot++;
    edge[tot].to=y;
    edge[tot].dis=z;
    edge[tot].next=head[x];
    head[x]=tot;
}
int add1(int x,int y,int z)
{
    tot1++;
    edge1[tot1].to=y;
    edge1[tot1].dis=z;
    edge1[tot1].next=head1[x];
    head1[x]=tot1;
}
int spfa(int s)
{
    for(int i=1;i<=n;i++) dis[i]=maxn,vis[i]=false;
    q.push(s),vis[s]=true,dis[s]=0;
    while(!q.empty())
    {
        x=q.front();q.pop(),vis[x]=false;
        for(int i=head[x];i;i=edge[i].next)
        {
            int t=edge[i].to;
            if(dis[t]>dis[x]+edge[i].dis)
            {
                dis[t]=dis[x]+edge[i].dis;
                if(vis[t]) continue;
                q.push(t),vis[t]=false;
            }
        }
    }
}
int spfa1(int s)
{
    for(int i=1;i<=n;i++) dis1[i]=maxn,vis[i]=false;
    q.push(s),vis[s]=true,dis1[s]=0;
    while(!q.empty())
    {
        x=q.front();q.pop(),vis[x]=false;
        for(int i=head1[x];i;i=edge1[i].next)
        {
            int t=edge1[i].to;
            if(dis1[t]>dis1[x]+edge1[i].dis)
            {
                dis1[t]=dis1[x]+edge1[i].dis;
                if(vis[t]) continue;
                q.push(t),vis[t]=false;
            }
        }
    }
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        x=read(),y=read(),z=read();
        add(x,y,z),add1(y,x,z);
    }
    spfa(1),spfa1(1);
    for(int i=2;i<=n;i++)
     ans+=(long long)dis[i]+dis1[i];
    printf("%lld",ans);
    return 0;
}
求从1点到所有点然后在从所有点返回1点的最短路的长度,注意我们一次只能到达一个点

洛谷——P1608 路径统计

题解:统计最短路径条数的时候,如果当前点t是由x更新的,那么到t的最短路条数=到达x点的最短路条数;如果dis[t]=dis[x]+edge[i].dis;最短路径+到达x点的最短路的条数

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 3010
#define maxn 999999
using namespace std;
queue<int>q;
bool vis[N];
int n,m,x,y,z,tot,sum[N],dis[N],f[N][N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
int spfa(int s)
{
    for(int i=1;i<=n;i++) dis[i]=maxn,vis[i]=false,sum[i]=1;
    dis[s]=0,vis[s]=true,q.push(s);
    while(!q.empty())
    {
        x=q.front(),q.pop();vis[x]=false;
        for(int t=1;t<=n;t++)
          if(f[x][t])
            if(dis[t]>dis[x]+f[x][t])
            {
                dis[t]=dis[x]+f[x][t];
                sum[t]=sum[x];
                if(vis[t]) continue;
                q.push(t),vis[t]=true;
            }
            else if(dis[t]==dis[x]+f[x][t]) sum[t]+=sum[x];
    }
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=n;i++)
     for(int j=1;j<=n;j++)
      f[i][j]=maxn;
    for(int i=1;i<=m;i++)
    {
        x=read(),y=read(),z=read();
        f[x][y]=min(f[x][y],z);
    }
    spfa(1);
    if(dis[n]==maxn) printf("No answer");
    else printf("%d %d",dis[n],sum[n]);
    return 0;
}
统计最短路径的条数

洛谷——P2196 挖地雷

题意:求从任意一个点开始可以挖到的地雷的总数最多,求地雷总数及挖的路径

题解:看数据范围n<=20所以我们可以跑n边spfa,枚举从每一个点开始所能挖到的最多的地雷的数目,如果当前路径的结尾挖到的地雷数最多,则说明这条道路最优,如果我们美剧到的路径更优,我们更新答案,更新路径

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 300
using namespace std;
queue<int>q;
bool vis[N];
int n,m,x,y,e,tot,ans,maxn,sum;
int a[N],fa[N],pre[N],dis[N],head[N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
struct Edge
{
    int to,dis,next;
}edge[N];
int add(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
int spfa(int s)
{
    for(int i=1;i<=n;i++) dis[i]=0,vis[i]=false;
    q.push(s),vis[s]=true,dis[s]=a[s];
    while(!q.empty())
    {
        x=q.front();q.pop(),vis[x]=false;
        for(int i=head[x];i;i=edge[i].next)
        {
            int t=edge[i].to;
            if(dis[t]<=dis[x]+a[t])
            {
                dis[t]=dis[x]+a[t];
                fa[t]=x;
                if(vis[t]) continue;
                q.push(t),vis[t]=true;
            }
        }
    }
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(x=1;x<n;x++)
    {
        for(int y=x+1;y<=n;y++)
        {
            e=read();
            if(e) add(x,y);
        }
    }
    for(int s=1;s<=n;s++)
    {
        maxn=ans;
        memset(fa,-1,sizeof(fa));
        spfa(s);
        for(int i=1;i<=n;i++) 
         if(ans<dis[i]) e=i,ans=dis[i];
        if(ans==maxn) continue;
        sum=0;
        for(;e!=-1;e=fa[e])
         pre[++sum]=e;
    }
    for(int i=sum;i>=1;i--)
     printf("%d ",pre[i]);
    printf("\n%d",ans);
    return 0;
}
求最长路经及其长度

洛谷——P2296 寻找道路

题意:在图中找一条从起点到终点的路径,该路径满足路径上的所有点的出边所指向的点都直接或间接与终点连通且路径最短。

题解:题目中要求我们找到的路径上的点所连得点都必须要与结点直接或间接相连。因此我们建反向边,然后bfs,搜索到终点不能到达的点,我们因此可以知道,这些点所连接的点都不能在我们所要找的道路中出现,然后我们在跑一边是spfa求一下最短路就好了

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 200100
using namespace std;
queue<int>q;
bool vis[N],vist[N];
int n,m,s,e,x,y,tot,dis[N],head[N];
int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
struct Edge
{
    int to,next;
}edge[N];
int add(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
int spfa(int s)
{
    memset(vis,0,sizeof(vis));
    memset(dis,0x3f3f3f3f,sizeof(dis));
    dis[s]=0,vis[s]=true;q.push(s);
    while(!q.empty())
    {
        int x=q.front(); q.pop(); vis[x]=false;
        for(int i=head[x];i;i=edge[i].next)
        {
            int to=edge[i].to;
            if(dis[to]<=dis[x]+1||vist[to]) continue;
            dis[to]=dis[x]+1;
            if(vis[to]) continue;
            q.push(to);    
            vis[to]=true;
        }
    }
}
int bfs(int s)
{
    vis[s]=true;q.push(s);
    while(!q.empty())
    {
        int x=q.front(); q.pop();
        for(int i=head[x];i;i=edge[i].next)
        {
            int to=edge[i].to;
            if(!vis[to]) q.push(to),vis[to]=true;
        }
    }
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
     x=read(),y=read(),add(y,x);
    s=read(),e=read();
    bfs(e);
    for(int i=1;i<=n;i++)
     if(vis[i]==false)
      for(int j=head[i];j;j=edge[j].next)
        vist[edge[j].to]=true;
    for(int i=1;i<=n;i++)
     if(!vis[i]) vist[i]=true;
    spfa(e);
    if(dis[s]>=0x3f3f3f3f) printf("-1");
    else printf("%d",dis[s]);
    return 0;
}
找出一条路径是路径上的点所连接的点都与终点直接或间接相连,并且使这条路径最短

洛谷——P2176 [USACO14FEB]路障Roadblock

题意:在 M 条路的某一条上安放一叠稻草堆,使这条路的长度加倍,选择一条路干扰使得FJ 从家到牛棚的路长增加最多,求最大增量

题解:我们知道更改最短路上的边才会使路径长度发生变化,因此我们先跑一遍最短路,然后记录最短路上的每一条路径,然后暴力枚举每一条路径将其路径长度增为两倍,然后在跑最短路,跑出的最短路与第一次的最短路相减即为答案

小总结:1、对于这种改变一条边,使s到t的路径增量最大,先跑最短路,记录路径,只有更改路径上的点才会使s到t的最短路长度发生改变,因此暴力枚举最短路上的每一条边,看那条边改变后造成的影响最大

2、改变图中一条边的信息,然后求最短路之类的问题,一般是建反向边,跑两遍spfa,然后最短路长度为从1点到这条路径s节点的路径+这条边的长度+终点到这条边的t节点的长度

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 5100
using namespace std;
queue<int>q;
bool vis[N];
int n,m,x,y,z,tot=1,ans1,ans2,sum;
int fa[N],dis[N],head[N],node[N],num[N];
int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f; 
}
struct Edge
{
    int to,dis,next,from;
}edge[N<<1];
int add(int x,int y,int z)
{
    tot++;
    edge[tot].to=y;
    edge[tot].dis=z;
    edge[tot].next=head[x];
    head[x]=tot;
}
int spfa(int s)
{
    memset(vis,0,sizeof(vis));
    memset(dis,0x3f3f3f3f,sizeof(dis));
    vis[s]=true,q.push(s),dis[s]=0;
    while(!q.empty())
    {
        int x=q.front();q.pop();vis[x]=false;
        for(int i=head[x];i;i=edge[i].next)
        {
            int to=edge[i].to;
            if(dis[to]>dis[x]+edge[i].dis)
            {
                dis[to]=dis[x]+edge[i].dis;
                fa[to]=x;num[to]=i;
                if(vis[to]) continue;
                q.push(to),vis[to]=true;
             }
        }
    }
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        x=read(),y=read(),z=read();
        add(x,y,z),add(y,x,z);
    }
    memset(fa,-1,sizeof(fa));
    spfa(1),ans1=dis[n];
    for(int i=n;i!=-1;i=fa[i])
      node[++sum]=num[i];
    for(int i=1;i<=sum;i++)
    {
        edge[node[i]].dis*=2;
        edge[node[i]^1].dis*=2;
        spfa(1);
        ans2=max(ans2,dis[n]);
        edge[node[i]].dis/=2;
        edge[node[i]^1].dis/=2;
    }
    printf("%d",ans2-ans1);
    return  0;
}
求使哪条边的长度改变使s到t的路径长度增量最大

洛谷——P1119 灾后重建

题意:给出每个村庄重建道路完成的时间,第i个村庄重建完成的时间t[i],可以认为是同时开始重建并在第t[i]天重建完成,并且在当天即可通车。若t[i]为0则说明地震未对此地区造成损坏,一开始就可以通车。之后有Q个询问(x, y, t),对于每个询问你要回答在第t天,从村庄x到村庄y的最短路径长度为多少

题解:求最短路径,无疑就是最短路的问题了,然后再看数据范围,看着数据范围就应该想到这个题要用Floyd。然后本题的t的单调递增的性质为本题降低了很大的难度,这样我们就不需要判断然后在处理了,在更新最短路的时候我们也不用k for循环到最后更新最短路了,因为他说t是单调的,我们直接一个while循环,从上一次k不能更新的位置直接开始,为什么?因为在这之前我们能跟新的已经跟新完了,如果在更新一遍就相当于做了无用功

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 210
#define maxn 0x3f3f3f3f
using namespace std;
int n,m,x,y,z,k,T,Q,t[N],dis[N][N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
int main()
{
    n=read(),m=read();k=1;
    for(int i=1;i<=n;i++) t[i]=read();
    for(int i=1;i<=n;i++)
     for(int j=1;j<=n;j++)
       dis[i][j]=maxn;
    for(int i=1;i<=m;i++)
    {
        x=read(),y=read(),z=read();
        dis[++x][++y]=dis[y][x]=z;
    }
    Q=read();
    while(Q--)
    {
        x=read(),y=read(),T=read();
        x++,y++;
        while(t[k]<=T&&k<=n)
        {
            for(int i=1;i<=n;i++)
             for(int j=1;j<=n;j++)
              dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
            k++;
        }
        if(dis[x][y]>=maxn||t[x]>T||t[y]>T) printf("-1\n");
        else printf("%d\n",dis[x][y]);
    }
    return 0;
}
View Code

2.spfa判负环

求一个点到另一个点的最短路径,当这个图中出现负环或两点间没有路径即为不存在从s点到t点的道路,如何判断是否存在负环?跑spfa,如果一个点入队次数超过n次则说明出现负环

洛谷—— P3385 【模板】负环

spfa判负环dfs版(dfs要比bfs的spfa判负环跑的快)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 200010
using namespace std;
bool vis[N],vist;
int n,m,t,x,y,z,tot,head[N],dis[N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
struct Edge
{
    int to,dis,next;
}edge[N<<1];
int add(int x,int y,int z)
{
    tot++;
    edge[tot].to=y;
    edge[tot].dis=z;
    edge[tot].next=head[x];
    head[x]=tot;
}
int spfa(int x)
{
    vis[x]=true;
    for(int i=head[x];i;i=edge[i].next)
    {
        int t=edge[i].to;
        if(dis[t]>dis[x]+edge[i].dis)
        {
            dis[t]=dis[x]+edge[i].dis;
            if(vis[t]||vist)
            {
                vist=true;
                break;
            }
            spfa(t);
        }
    }
    vis[x]=false;
}
void begin()
{
    tot=vist=0;
    memset(dis,0,sizeof(dis));
    memset(head,0,sizeof(head));
    memset(vis,false,sizeof(vis));
}
int main()
{
    t=read();
    while(t--)
    {
        begin();
        n=read(),m=read();
        for(int i=1;i<=m;i++)
        {
            x=read(),y=read(),z=read();
            add(x,y,z);
            if(z>=0) add(y,x,z);
        } 
        for(int i=1;i<=n;i++)
        {
            spfa(i);
            if(vist) break;
        }
        if(vist) printf("YE5\n");
        else printf("N0\n");
    }
    return 0;
}
dfs的spfa判负环

codevs——2645 Spore

为从星系1 到星系N 的最小代价的路线的代价. 如果这样的路线不存在,输出'No such path'.                     spfa判负环裸题,bfs版     线路不存在即为出现了负环

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 200010
#define maxn 9999999
using namespace std;
queue<int>q;
bool vis[N],vist;
int n,m,w,x,y,z,tot,head[N],dis[N],in[N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
struct Edge
{
    int to,dis,next;
}edge[N<<1];
int add(int x,int y,int z)
{
    tot++;
    edge[tot].to=y;
    edge[tot].dis=z;
    edge[tot].next=head[x];
    head[x]=tot;
}
void begin()
{
    tot=vist=0;
    memset(in,0,sizeof(in));
    memset(dis,0,sizeof(dis));
    memset(vis,0,sizeof(vis));
    memset(head,0,sizeof(head));
}
int spfa(int s)
{
    while(!q.empty()) q.pop();
    for(int i=1;i<=n;i++) dis[i]=maxn,vis[i]=false;
    vis[s]=true,dis[s]=0,q.push(s);
    while(!q.empty())
    {
        x=q.front(),q.pop();vis[x]=false;
        for(int i=head[x];i;i=edge[i].next)
        {
            int t=edge[i].to;
            if(dis[t]>dis[x]+edge[i].dis)
            {
                dis[t]=dis[x]+edge[i].dis;
                if(!vis[t]) in[t]++,vis[t]=true,q.push(t);
                if(in[t]>n) return true;
            }
        }
    }
    return false;
}
int main()
{
     while(1)
    {
        begin();
        n=read(),m=read();
        if(n==0&&m==0) break;
        for(int i=1;i<=m;i++)
        {
            x=read(),y=read(),z=read(),w=read();
            add(x,y,z),add(y,x,w);
        }
        vist=spfa(1);
        if(vist||dis[n]==maxn) printf("No such path\n");
        else  printf("%d\n",dis[n]);
    }
    return 0;
}
bfs判负环,不要忘了初始化!

洛谷——P2868 [USACO07DEC]观光奶牛Sightseeing Cows

题解:奶牛们从起点出发然后在回到起点,也就是说奶牛走过的路径为一个环,在奶牛走的这个环中ans=所有的乐趣数/路上消耗的所有的时间。

我们将上面的式子进行变形,可以得到路上消耗的所有时间*ans=所有的乐趣数。——>路上消耗的所有时间*ans-所有的乐趣数=0;

然后我们在进行二分答案,处理出ans,然后对于每一个ans跑n个spfa,判断是否存在负环,如果存在负环就说明当前方案不是最佳答案(存在负环条件:当前点入队次数大于n,当然这种情况是当我们用bfs的spfa时),让我们用dfs的spfa时,我们用一个bool变量表示这个数有没有被访问过,如果被访问过,说明他出现了负环直接结束循环。我们的ans还能有更大的解,即当路上消耗的所有的时间*ans-所有的乐趣数<0时我们的ans不是当前最优解继续更新最优解

注意:二分结束的条件为r-l>0.000001,否则会爆long long

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 51000
using namespace std;
bool vis[N];
int n,m,x,y,z,tot;
int c[N],num[N],head[N];
double ans,mid,l,r,w[N],dis[N];
struct Edge
{
    int to,dis,from,next;
}edge[N];
int add(int x,int y,int z)
{
    tot++;
    edge[tot].to=y;
    edge[tot].dis=z;
    edge[tot].next=head[x];
    head[x]=tot;
}
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*10+ch-'0'; ch=getchar();}
    return x*f;
}
int spfa(int x)
{
    vis[x]=true;
    for(int i=head[x];i;i=edge[i].next)
    {
        int t=edge[i].to;
        if(dis[t]>dis[x]+w[i])
        {
            dis[t]=dis[x]+w[i];
            if(vis[t]||spfa(t))
            {
                vis[x]=false;
                return true;
            }  
        }
    }
    vis[x]=false;
    return false;
}
int pd()
{
    for(int i=1;i<=n;i++)
        if(spfa(i)) return true;
    return false;
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=n;i++) c[i]=read();
    for(int i=1;i<=m;i++)
    {
        x=read(),y=read(),z=read();
        add(x,y,z);
    }
    l=0,r=100005;
    while(r-l>0.0000001)
    {
        mid=(l+r)/2;
        for(int i=1;i<=tot;i++)
        {
            int t=edge[i].to;
            w[i]=(double)mid*edge[i].dis-c[t];
        }
        if(pd())
        {
            ans=mid;
            l=mid;
        }
        else r=mid;
    }
    printf("%.2lf",ans);
    return 0;
}
二分+dfs判负环

 

         五、拓扑排序

一般是用来求给定n组顺序关系(名次,辈分,大小、、、),判断给出的信息是否存在矛盾,求出符合条件的顺序,如需要求出最小的则需要使用大根堆。拓扑排序还可以用来找一个图中的最长链,也可以用来找从一个点出发的最长链(经过一个点的最长链)不过要先预处理一下

HDU——1285 确定比赛名次

给出n个名次关系,求出符合条件的排名顺序,输出字典序最小的答案

拓扑排序模板题,使用优先队列来使答案的字典序最小

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 510
using namespace std;
priority_queue<int,vector<int>,greater<int> >q;
int n,m,x,y,tot,sum,in[N],ans[N],head[N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
struct Edge
{
    int to,dis,next;
}edge[N];
int add(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
int tpsort()
{
    for(int i=1;i<=n;i++)
     if(in[i]==0) q.push(i);
    while(!q.empty())
    {
        x=q.top(),q.pop(),sum++,ans[sum]=x;
        for(int i=head[x];i;i=edge[i].next)
        {
            int t=edge[i].to;
            in[t]--;
            if(in[t]==0) q.push(t);
        }
    }
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        sum=0,tot=0;
        memset(in,0,sizeof(in));
        memset(ans,0,sizeof(ans));
        memset(head,0,sizeof(head));
        memset(edge,0,sizeof(edge));
        for(int i=1;i<=m;i++)
         x=read(),y=read(),add(x,y),in[y]++;
        tpsort();
        for(int i=1;i<sum;i++)
         printf("%d ",ans[i]);
        printf("%d\n",ans[sum]);
    }
    return 0;
}
给定名次关系,求排列顺序

HDU——3342 Legal or Not

给出n对顺序关系,询问所给出的关系是否合法,即为不互相矛盾

题解:拓扑排序判环,当拓扑排序入队的点的个数少于n时,即为出现了环

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 510
using namespace std;
queue<int>q;
int n,m,x,y,tot,sum,in[N],head[N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
struct Edge
{
    int to,next;
}edge[N];
int add(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
bool tpsort()
{
    for(int i=1;i<=n;i++) 
     if(in[i]==0) q.push(i);
    while(!q.empty())
    {
        x=q.front(),q.pop(),sum++;
        for(int i=head[x];i;i=edge[i].next)
        {
            int t=edge[i].to;
            in[t]--;
            if(in[t]==0) q.push(t);
        }
    }    
    if(sum==n) return true;
    return false;
}
int main()
{
    while(1)
    {
        n=read(),m=read();
        if(n==0&&m==0) break;
        tot=0,sum=0;
        memset(in,0,sizeof(in));
        memset(head,0,sizeof(head));
        memset(edge,0,sizeof(edge));
        for(int i=1;i<=m;i++)
         x=read(),y=read(),add(x+1,y+1),in[y+1]++;
        if(tpsort()) printf("YES\n");
        else printf("NO\n");
    }
    return  0;
}
给出m对顺序的关系,询问给出的关系是否矛盾

HDU——2647 Reward

老板要发工资,每个人都有一个要求,他必须比谁的工资高,基准工资为888,求共最少需要发多少工资

题解:拓扑排序分层考虑,每一层一种工资,怎么判断是那一层?对于基层我们是知道他在哪一层的,然后我们每一个入队的点的层数一定是链接他的那个在队中的点的层数+1

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 20100
#define money 888
using namespace std;
queue<int>q;
long long anss;
int n,m,x,y,s,in[N],tot,sum,head[N],ans[N];
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*10+ch-'0'; ch=getchar();}
    return x*f;
}
struct Edge
{
    int from,to,next;
}edge[N];
int add(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
void begin()
{
    s=0,sum=0,tot=0;anss=0;
    memset(in,0,sizeof(in));
    memset(ans,0,sizeof(ans));
    memset(head,0,sizeof(head));
    memset(edge,0,sizeof(edge));
}
int tpsort()
{
    for(int i=1;i<=n;i++)
     if(in[i]==0) q.push(i),ans[i]=0;
    while(!q.empty())
    {
        x=q.front();q.pop(),sum++;
        for(int i=head[x];i;i=edge[i].next)
        {
            int t=edge[i].to;
            in[t]--;
            if(in[t]==0) ans[t]=ans[x]+1,q.push(t);
        }
    }
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        begin();
        for(int i=1;i<=m;i++)
        {
            x=read(),y=read();
            add(y,x),in[x]++;
        }
        tpsort();
        if(sum!=n) printf("-1\n");
        else 
        {
            for(int i=1;i<=n;i++)
             anss+=money+ans[i];
            printf("%lld\n",anss);
        }       
    }
    return  0;
}
每一层上的人一种工资,求最少工资总数

 

洛谷—— P1347 排序

题意:给你一系列形如A<B的关系,并要求你判断是否能够根据这些关系确定这个数列的顺序

题解:拓扑排序+模拟

#include<queue>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 110
using namespace std;
int a,b,n,m,s,sum,tot,head[N],in[N],inn[N],p[N];
bool v,unpd,vis[N];
queue<int>q;
char ch;
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*10+ch-'0';ch=getchar();}
    return f*x;
}
struct Edge
{
    int from,to,next;
}edge[N];
int add(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}//和以前的拓扑排序过程一样,只是多加了几种情况 
int tp()
{
    unpd=false;    v=false; sum=0;//初始数值 
    for(int i=1;i<=26;i++)//开始找入读为一的点 
     {
         inn[i]=in[i];//由于我们要没输入一组后就对该序列进行判断,所以我们新设一个数组inn储存in中的各点入度的值,防止我们下一次在用时,该店的入度值已不是初始值 
         if(!inn[i]&&vis[i])//该点的入读为0并且我们输入过该值 
          {
              if(!v) v=true;//我们要判断有几个入读为0的点,由于如果有两个入读为0的点我们则无法判断他们的关系因为入读为0的点一定是小的,但这两个值得大小我们又无法判断 
              else unpd=true;//unpd用来判断无法判段的情况 
              q.push(i);
              p[++sum]=i;
           } 
      } 
    if(q.empty()) return 1;//如果q数组为空,就说明出现了环,则是存在矛盾的情况。 
    while(!q.empty())//单纯的拓扑排序,但我们要在里面多加一点东西:和前面判断入读为0的方法一样,如果删除一个点以后出现了两个入度为0的边,这样我们将无法判断这两个点的大小 
    {
        int x=q.front();v=false;q.pop();
        for(int i=head[x];i;i=edge[i].next)
        {
            inn[edge[i].to]--;
            if(!inn[edge[i].to])
            {
                q.push(edge[i].to);
                if(!v) v=true;
                else unpd=true;
                p[++sum]=edge[i].to;
            } 
        }
    }
    if(sum!=s) return 1;//说明出现了环。 
    if(unpd) return 2;
    return 0; 
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        cin>>ch,a=ch-64;if(!vis[a]) vis[a]=true,s++;//s是用来存我们输入的元素的个数,方便后面判断s值与sum值的关系(来判断是否为环) 
        cin>>ch;//这个在输入时其实我们也可以用一个数组来表示,在这里我们由于有一个<是没有用的,所以我们直接输入就好了 
        cin>>ch,b=ch-64;if(!vis[b]) vis[b]=true,s++;//vis用来表示该数有值 
        add(a,b);//在这里我们将我们输入的字符转化成了数字,方便后面进行操作 
        in[b]++;//储存入读 
        if(tp()==1) //在这里我们必须让他等于1,因为我们在tp函数中返回的是0,1,2 
        {
            printf("Inconsistency found after %d relations.",i);//存在矛盾 
            return 0;
        }
        if(sum==n&&!tp())//sum=n,说明该序列中所有的数都进行了排序,都能确定他们的位置 
        {
            printf("Sorted sequence determined after %d relations: ",i);
            for(int j=1;j<=n;j++) printf("%c",p[j]+64);//在最开始的时候我竟然让他输出p[i]+64.这告诉我们要注意我们循环使用的变量i,j 
            printf(".");
            return 0;
        }
    }
    printf("Sorted sequence cannot be determined.");//由于我们在tp函数中只有三种情况,除了前两种剩下的就是这一种了。 
    return 0;
}
给定一些关系问是否能通过给出的关系判断所有元素的关系,如果不能问在第几步后关系就出现了矛盾

洛谷——P3119 [USACO15JAN]草鉴定Grass Cownoisseur

 法一:建反向边,双向dfs找从1点能够达到的最长链,能够到达1点的最长链,然后枚举需要反转的边,若能更新最大值,则更新最大值。

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 210000
using namespace std;
queue<int>q;
int n,m,x,y,s,tot,tot1,top,tim;
bool vis[N],vis1[N],vis2[N],vist1[N],vist2[N];
int xx[N],yy[N],in1[N],in2[N],head1[N],head2[N],dfn[N];
int low[N],sum[N],ans1[N],ans2[N],head[N],stack[N],belong[N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
struct Edge
{
    int to,next;
}edge[N],edge1[N],edge2[N];
int add(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
int add1(int x,int y)
{
    tot1++;
    edge1[tot1].to=y;
    edge1[tot1].next=head1[x];
    edge2[tot1].to=x;
    edge2[tot1].next=head2[y];
    head1[x]=head2[y]=tot1;
}
int tarjan(int x)
{
    dfn[x]=low[x]=++tim;
    stack[++top]=x,vis[x]=true;
    for(int i=head[x];i;i=edge[i].next)
    {
        int t=edge[i].to;
        if(vis[t]) low[x]=min(low[x],dfn[t]);
        else if(!dfn[t]) tarjan(t),low[x]=min(low[t],low[x]);
    }
    if(low[x]==dfn[x])
    {
        s++,sum[s]++,belong[x]=s;
        for(;stack[top]!=x;top--)
        {
            sum[s]++;
            vis[stack[top]]=false;
            belong[stack[top]]=s;
        }
        top--,vis[x]=false;
    }
}
int shink_point()
{
    for(int i=1;i<=n;i++)
     for(int j=head[i];j;j=edge[j].next)
     {
         int t=edge[j].to;
         if(belong[i]!=belong[t]) 
          add1(belong[i],belong[t]);
     }
}
int dfs1(int x)
{
    vis1[x]=true;
    for(int i=head1[x];i;i=edge1[i].next)
    {
        int t=edge1[i].to;
        if(ans1[t]<ans1[x]+sum[t])
         ans1[t]=ans1[x]+sum[t],dfs1(t);
    }
}
int dfs2(int x)
{
    vis2[x]=true;
    for(int i=head2[x];i;i=edge2[i].next)
    {
        int t=edge2[i].to;
        if(ans2[t]<ans2[x]+sum[t])
         ans2[t]=ans2[x]+sum[t],dfs2(t);
    }
}
int main()
{
     n=read(),m=read();
    for(int i=1;i<=m;i++)
     xx[i]=read(),yy[i]=read(),add(xx[i],yy[i]);
    for(int i=1;i<=n;i++)
     if(!dfn[i]) tarjan(i);
    shink_point();
    ans1[belong[1]]=ans2[belong[1]]=sum[belong[1]];
    dfs1(belong[1]);dfs2(belong[1]);
    int answer=2*sum[belong[1]];
    for(int i=1;i<=m;i++)
    {
        x=belong[yy[i]],y=belong[xx[i]];
        if(vis1[x]&&vis2[y])
         answer=max(answer,ans1[x]+ans2[y]);
    }
    printf("%d",answer-sum[belong[1]]);
    return 0;
}
求将一条边反转以后图中的最长链的长度

法二:拓扑排序求最长链

们如果直接进行拓扑排序的话,我们会意识到一个问题:缩完点以后直接统计出来入度为零的点并非是我们所需要的点1,我们要跑最长链的话我们需要从1点开始跑,也就是说我们的起点必须是1,怎样做到这一点??我们要做到起点是一的话我们必须让1的入度为零,从一点开始更新与他相连的点。从新统计他们的入读,也就是说我们将这个可能出现环的图抽离成一颗树,这棵树的树根为1点。然后再进行拓扑排序,找出最长链。

#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 210000
using namespace std;
int n,m,x,y,s,tot,tat,top,tim;
bool vis[N],vis1[N],vis2[N];
int xx[N],yy[N],in1[N],in2[N],head1[N],head2[N],dfn[N];
int low[N],sum[N],ans1[N],ans2[N],head[N],stack[N],belong[N];
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*10+ch-'0'; ch=getchar();}
    return x*f;
}
struct Edge
{
    int to,from,next;
}edge[N],edge1[N],edge2[N];
int add(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
int add1(int x,int y)
{
    tat++;
    edge1[tat].to=y;
    edge1[tat].next=head1[x];
    edge2[tat].to=x;
    edge2[tat].next=head2[y];
    head1[x]=head2[y]=tat;
}
int tarjan(int now)
{
    dfn[now]=low[now]=++tim;
    vis[now]=true;stack[++top]=now;
    for(int i=head[now];i;i=edge[i].next)
    {
        int t=edge[i].to;
        if(vis[t]) low[now]=min(dfn[t],low[now]);
        else if(!dfn[t]) tarjan(t),low[now]=min(low[t],low[now]);
    }
    if(low[now]==dfn[now])
    {
        s++,belong[now]=s,sum[s]++;
        for(;stack[top]!=now;top--)
          belong[stack[top]]=s,sum[s]++,vis[stack[top]]=false;
        vis[now]=false;top--;    
    }
}
int shink_point()
{
    for(int i=1;i<=m;i++)
     for(int j=head[i];j;j=edge[j].next)
      if(belong[i]!=belong[edge[j].to])
          add1(belong[i],belong[edge[j].to]);  
}
int dfs1(int s)
{
    for(int i=head1[s];i;i=edge1[i].next)
    {
        int t=edge1[i].to;
        if(!in1[t]) dfs1(t);
        in1[t]++;
    }
}
int dfs2(int s)
{
    for(int i=head2[s];i;i=edge2[i].next)
    {
        int t=edge2[i].to;
        if(!in2[t]) dfs2(t);
        in2[t]++;
    }
}
int tpsort(int *in,Edge *edge,int *head,bool *vis,int *ans)
{
    queue<int>q;
    q.push(belong[1]);
    while(!q.empty())
    {
        int x=q.front();q.pop();vis[x]=true;
        for(int i=head[x];i;i=edge[i].next)
        {
            int t=edge[i].to;
            in[t]--;
            if(!in[t]) q.push(t);
            ans[t]=max(ans[t],ans[x]+sum[t]);
        }
    }
}
int main()
{
    n=read(),m=read();
    int answer=0;
    for(int i=1;i<=m;i++)
     xx[i]=read(),yy[i]=read(),add(xx[i],yy[i]);
    for(int i=1;i<=n;i++)
     if(!dfn[i]) tarjan(i);
    shink_point();
    dfs1(belong[1]),dfs2(belong[1]);
    ans1[belong[1]]=ans2[belong[1]]=sum[belong[1]];
    tpsort(in1,edge1,head1,vis1,ans1);
    tpsort(in2,edge2,head2,vis2,ans2);
    answer=2*sum[belong[1]];
    for(int i=1;i<=m;i++)
    {
        x=belong[yy[i]],y=belong[xx[i]];
        if(vis1[x]&&vis2[y])
         answer=max(answer,ans1[x]+ans2[y]);
    }
    printf("%d",answer-sum[belong[1]]);
    return 0;
}
拓扑排序求最长链

          六、二分图

最小顶点覆盖是指最少的顶点数使得二分图中的每条边都至少与其中一个点相关联,二分图的最小顶点覆盖数=二分图的最大匹配数;

最小路径/点覆盖是指用尽量少的不相交路径覆盖二分图中的所有顶点。二分图的最小路径覆盖数=|V|-二分图的最大匹配日数    

最大独立集(指寻找一个点集,使得其中任意两点在图中无对应边)=|V|-二分图的最大匹配数

洛谷——P3386 【模板】二分图匹配

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 1010
using namespace std;
bool vis[N];
int n,m,e,x,y,ans,girl[N],map[N][N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
int find(int x)
{
    for(int i=1;i<=m;i++)
     if(map[x][i]&&!vis[i])
     {
         vis[i]=true;
         if(find(girl[i])||!girl[i])
         {
             girl[i]=x;
             return true;
         }
     }
     return false;
}
int main()
{
    n=read(),m=read(),e=read();
    for(int i=1;i<=e;i++)
     x=read(),y=read(),map[x][y]=1;
    for(int i=1;i<=n;i++)
    {
        memset(vis,0,sizeof(vis));
        if(find(i)) ans++;
    }
    printf("%d",ans);
    return 0;
}
二分图最大匹配

poj——3041    Asteroids

题意:给你一个N*N的矩阵,有一些格子里有小行星,现在你有一些威力很大的炮弹,每一次射击都能够消灭掉矩阵中一行或一列的小行星,求最少射击次数

题解:行列匹配,对于这种每次只能消灭一行或者一列的操作,我们可以将每一行看成一个集合,将每一列看成一个集合,然后跑二分图匹配,题目中要求最少多少步可以将小星星全部消灭,就是让求最少用多少条边将所有的点全部覆盖,也就是最小点覆盖问题=最大匹配数,然后就是个模板题了

 

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 1010
using namespace std;
bool vis[N];
int n,m,e,x,y,ans,girl[N],map[N][N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
int find(int x)
{
    for(int i=1;i<=n;i++)
     if(map[x][i]&&!vis[i])
     {
         vis[i]=true;
         if(find(girl[i])||!girl[i])
         {
             girl[i]=x;
             return 1;
         }
     }
    return 0;
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
     x=read(),y=read(),map[x][y]=1;
    for(int i=1;i<=n;i++)
    {
        memset(vis,0,sizeof(vis));
        if(find(i)) ans++;
    }
    printf("%d",ans);
    return 0;
 } 
行列匹配:每次只能消灭一行或者一列,问最少多少步可以将。。全部消灭

 

poj —— 1274 The Perfect Stall

题意:给出一些喜欢关系,一头牛只能选则一个摊位,牛只有在自己喜欢的摊位上才能产奶,问最多产奶总数

小总结:对于喜欢类型(或者一个点可以选择另一个点)的问题,求最多能满足的人数(对数)一般是二分图求最大匹配裸题

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 1010
using namespace std;
bool vis[N];
int n,m,k,x,y,ans,girl[N],map[N][N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
int find(int x)
{
    for(int i=1;i<=m;i++)
     if(map[x][i]&&!vis[i])
     {
         vis[i]=true;
         if(find(girl[i])||girl[i]==-1)
         {
             girl[i]=x;
             return 1;
         }
     }
     return 0;
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        ans=0;
        memset(map,0,sizeof(map));
        memset(girl,-1,sizeof(girl));
        for(int i=1;i<=n;i++)
        {
            k=read();
            while(k--) x=read(),map[i][x]=1;
        }
        for(int i=1;i<=n;i++)
        {
            memset(vis,0,sizeof(vis));
            if(find(i)) ans++;
        }
        printf("%d\n",ans);
    }
    return 0;
 } 
最多满足的人数

 HDU——1150 Machine Schedule

题意:给出两台机器A、B,机器A上有n种模式,机器B上有m种模式,现有k个需要运行的任务,每个任务有对应的运行模式,(i, x, y)表示i任务对应的A B机器上的运行模式为x,y. 开始的工作模式都是0.每台机器上的任务可以按照任意顺序执行,但是每台机器每转换一次模式需要重启一次。求机器重启的最少次数

题解:我们对于每一个任务连边,然后我们现在要完成任务并且我们要要求机器重启的次数最少,那么也就是说我们需要用到最少的点把所有的边连起来,这样就转化成了最小点覆盖的裸题了                 最小点覆盖等于最大匹配数

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 1010
using namespace std;
bool vis[N];
int n,m,k,x,y,z,ans,girl[N],map[N][N];
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*10+ch-'0'; ch=getchar();}
    return x*f;
}
int find(int x)
{
    for(int i=1;i<=m;i++)
    {
        if(!vis[i]&&map[x][i])
        {
            vis[i]=true;
            if(girl[i]==-1||find(girl[i])) {girl[i]=x; return 1;}
        }
    }
    return 0;
}
int main()
{
    while(1)
    {
        n=read();if(n==0) break;
        m=read(),k=read();ans=0;
        memset(map,0,sizeof(map));
        memset(girl,-1,sizeof(girl));
        for(int i=1;i<=k;i++)
        {
            z=read(),x=read(),y=read();
            map[x][y]=1;
        }
        for(int i=1;i<=n;i++)
        {
            memset(vis,0,sizeof(vis));
            if(find(i)) ans++;
        }
        printf("%d\n",ans);
    }
    return 0;
}
最小点覆盖

HDU——2768 Cat vs. Dog

题意:喜欢猫的一定不喜欢狗,喜欢狗的一定不喜欢猫,我们要选则养猫还是养狗,使满足的人最多

题解: 我们将喜欢猫的和喜欢狗的划分成两个集合,然后将这两个集合中存在矛盾的点连边,构图,跑二分图匹配,答案及为最大独立集,最大独立集=总点数-最大匹配数

小总结:给出n对喜欢关系,喜欢A的不喜欢B,喜欢B的不喜欢A,我们选择一个人,问最多能是多少个人满足,这个时候我们将喜欢A的看成一个几何,喜欢B的看成一个集合,然后将这两个集合中存在矛盾的连边,跑最大独立集

HDU——2444 The Accomodation of Students

题目:一些学生之间是朋友关系(关系不能传递),问能否将学生分成两堆使得同一堆的学生之间不是朋友。如果不可以输出“No”,可以的话输出最多可以分出几对小盆友。

题解:先二分图染色判断其是否是二分图,然后在跑最大匹配

#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 510
using namespace std;
bool flag,vis[N];
int n,m,x,y,tot,ans,col[N],girl[N],head[N],map[N][N];
queue<int>q;
struct Edge
{
    int from,to,next;
}edge[N*N];
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*10+ch-'0'; ch=getchar();}
    return x*f;
}
int add(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
int find(int x)
{
    for(int i=1;i<=n;i++)
    {
        if(!vis[i]&&map[x][i])
        {
            vis[i]=true;
            if(girl[i]==-1||find(girl[i])){girl[i]=x; return 1;}
        }
    }
    return 0;
}
int color(int s)
{
    queue<int>q;
    q.push(s); col[s]=0;
    while(!q.empty())
    {
        int x=q.front();
        for(int i=head[x];i;i=edge[i].next)
        {
            int t=edge[i].to;
            if(col[t]!=-1){if(col[t]==col[x]) {flag=true; return 1;}}
            else
            {
                col[t]=col[x]^1;
                q.push(t);
            }
        }
        q.pop();
    }
    return 0;
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        ans=0;flag=false;tot=0;
        memset(map,0,sizeof(map));
        memset(col,-1,sizeof(col));
        memset(edge,0,sizeof(edge));
        memset(head,0,sizeof(head));
        for(int i=1;i<=m;i++)
        {
            x=read(),y=read();
            map[x][y]=1;
            add(x,y),add(y,x);
        }
        for(int i=1;i<=n;i++)
         if(col[i]==-1)
         {
             if(color(i)) break;
          } 
        if(flag) {printf("No\n"); continue;}
        memset(girl,-1,sizeof(girl));
        for(int i=1;i<=n;i++)
        {
            memset(vis,0,sizeof(vis));
            if(find(i)) ans++;
        }
        printf("%d\n",ans);
    }
    return 0;
}
二分图染色+最大匹配数

洛谷——P2756 飞行员配对方案问题

KM算法

         六、LCA

洛谷——P3379 【模板】最近公共祖先(LCA)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 501000
using namespace std;
int n,m,tot,x,y,root;
int fa[N],top[N],size[N],deep[N],head[N];
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*10+ch-'0',ch=getchar();
    return x*f;
}
struct Edge
{
    int to,next;
}edge[N<<1];
int add(int x,int y)
{
    tot++;
    edge[tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
}
int dfs(int x)
{
    size[x]=1;
    deep[x]=deep[fa[x]]+1;
    for(int i=head[x];i;i=edge[i].next)
    {
        int t=edge[i].to;
        if(fa[x]==t) continue;
        fa[t]=x,dfs(t);
        size[x]+=size[t];
    }
}
int dfs1(int x)
{
    int t=0;
    if(!top[x]) top[x]=x;
    for(int i=head[x];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if(fa[x]!=to&&size[t]<size[to]) t=to;
    }
    if(t) top[t]=top[x],dfs1(t);
    for(int i=head[x];i;i=edge[i].next)
    {
        int to=edge[i].to;
        if(fa[x]!=to&&to!=t) dfs1(to);
    }
}
int LCA(int x,int y)
{
    for(;top[x]!=top[y];x=fa[top[x]])
     if(deep[top[x]]<deep[top[y]])
      swap(x,y);
    if(deep[x]>deep[y]) swap(x,y);
    return x;
}
int main()
{
    n=read(),m=read(),root=read();
    for(int i=1;i<n;i++)
     x=read(),y=read(),add(x,y),add(y,x);
    dfs(root),dfs1(root);
    for(int i=1;i<=m;i++)
    {
        x=read(),y=read();
        printf("%d\n",LCA(x,y));
    }
    return 0;
}
LCA模板

 

转载于:https://www.cnblogs.com/z360/p/7789166.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值