拓扑排序——奖金(差分约束和强连通分量和拓扑序三种解法)

传送门:奖金

题意:

由题意可以很容易发现,这是一道可以用差分约束来解的题目,比一般的差分约束裸题都简化了很多。建好边之后用spfa求最长路径即可,存在负环就无解。

用强连通分量来想的话就是最后缩完点之后也是一个拓扑图,判断有无解就是看有没有哪一个强连通分量存在边权不为0的边,不过这道题目建边的边权全都是1,意味着只要在一个强连通分量里面有两个或以上的节点就一定无解,也就是说最后强连通分量编号如果不等于n的话就无解。

拓扑排序思路:按照要求建边后走一遍拓扑排序得到拓扑序列。遍历该序列,对每一个点的出点都更新最长路。

为什么是最长路而不是最短路呢?

理由:和差分约束求最小值时用最长路来求一样,里面的点可能会有多个下限,为了满足所有的下限,必须从中挑出一个最大的作为答案。

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=1e4+10,M=2e4+10;
int idx,e[M],h[N],ne[M],w[M];
int d[N];
int q[N];
int dist[N];
int n,m;
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
bool topsort()
{
    int hh=0,tt=-1;
    for(int i=1;i<=n;i++)
    {
        if(d[i]==0)
        {
            q[++tt]=i;
        }
    }
    while(hh<=tt)
    {
        int t=q[hh++];
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];
            d[j]--;
            if(d[j]==0) q[++tt]=j;
        }
    }
    return tt==n-1;
}
int main()
{
    memset(h,-1,sizeof h);
    scanf("%d%d",&n,&m);
    int a,b;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&a,&b);
        add(b,a);
        d[a]++;
    }

    if(!topsort()) puts("Poor Xed");
    else
    {
        for(int i=1;i<=n;i++)
            dist[i]=100;
        for(int i=0;i<n;i++)
        {
            int j=q[i];
            for(int k=h[j];~k;k=ne[k])
                dist[e[k]]=max(dist[e[k]],dist[j]+1);
        }
        int sum=0;
            for(int i=1;i<=n;i++)
                sum+=dist[i];
            printf("%d",sum);
    }


    return 0;
}

差分约束代码(写好了):

思路:这道题的不等式关系只有一种,就是a>=b+1,则只要连一条从b到a的边权为1的边即可,

题目有规定最小的奖金是100,并且整个图不一定完全连通,所以要建立一个超级源点0向所有的点连一条长度为100的边。用一个数组记录每个点的入队次数,超过n次说明存在负环则无解。

代码:

#include <iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e4+10.,M=4e4+10;
int idx,e[M],h[N],ne[M],w[M];
int d[N];
int q[N];
int n,m;
int dist[N];
bool st[N];
int cnt[N];
void add(int a,int b,int c)
{
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
bool spfa()
{
    memset(dist,-0x3f,sizeof dist);
    dist[0]=0;
    st[0]=true;
    int hh=0,tt=1;
    while(hh!=tt)
    {
        int t=q[hh++];
        st[t]=false;
        if(hh==N) hh=0;
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];

            if(dist[j]<dist[t]+w[i])
            {
                dist[j]=dist[t]+w[i];
                cnt[j]=cnt[t]+1;
                if(cnt[j]>=n+1) return false;
               if(!st[j])
               {
                   q[tt++]=j;
                   if(tt==N) tt=0;
                   st[j]=true;
               }
            }
        }
    }
    return true;
}
int main()
{
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    while(m--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(b,a,1);
    }
    for(int i=1;i<=n;i++)
    add(0,i,100);

    if(!spfa()) puts("Poor Xed");
    else
    {
        int sum=0;
        for(int i=1;i<=n;i++)
            sum+=dist[i];
        cout<<sum<<endl;
    }

    return 0;
}

强连通分量写法:

思路:tarjan算法主要用来判断有无解以及得到一个拓扑序,因为这道题目的特殊性,每一个强连通分量都只能是由一个点构成,最后缩完点后的拓扑图也是有n个点才有合法解,不够n个就判断吴无解。

因为tarjan算法得到的拓扑图上的点本身就是按照点的个数逆序来就是拓扑序,所以不用再做一遍拓扑排序,除了要用一个id数组记录每一个节点的强连通分量的编号,还要同时增加一个id2数组记录每一个编号对应的原图的点,这个id2是因为这道题目的特殊性才能这么用,一般题目的强连通分量会有多个编号就不能这么搞了???好像也可以,用一个二维数组来存好像也行,但是图个啥呢?

#include <iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e4+10.,M=4e4+10;
int idx,e[M],h[N],ne[M],w[M];
int d[N];
int n,m;
int dist[N];
int dfn[N];
int low[N];
int stk[N],top;
int id[N];
int id2[N];
int times,scc_cnt;
bool in_stk[N];
void add(int a,int b,int c)
{
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void tarjan(int u)
{
    dfn[u]=low[u]=++times;
    stk[++top]=u,in_stk[u]=true;
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(!dfn[j])
        {
            tarjan(j);
            low[u]=min(low[u],low[j]);
        }else if(in_stk[j])
            low[u]=min(low[u],dfn[j]);
    }
    if(dfn[u]==low[u])
    {
        int y;
        ++scc_cnt;
        do{
            y=stk[top--];
            in_stk[y]=false;
            id[y]=scc_cnt;
            id2[scc_cnt]=y;
        }while(y!=u);
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);
    while(m--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        add(b,a,1);
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])
        tarjan(i);

    if(scc_cnt!=n)
    {
        puts("Poor Xed");
    }else
    {
        for(int t=scc_cnt;t>=1;t--)
        {
            for(int i=h[id2[t]];~i;i=ne[i])
            {
                int j=e[i];
                dist[j]=max(dist[j],dist[id2[t]]+1);
              //  cout<<j<<' '<<dist[j]<<endl;
            }
        }
        int sum=n*100;
        for(int i=1;i<=n;i++)
        {
            sum+=dist[i];
        }
        cout<<sum<<endl;
    }

    return 0;
}

tnnd,用的数组一个比一个多。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值