糖果(SPFA、判负环)

Description
幼儿园里有 N 个小朋友,老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候,老师需要满足小朋友们的 K 个要求。幼儿园的糖果总是有限的,老师想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。

Input
输入的第一行是两个整数 N,K。接下来 K 行,表示这些点需要满足的关系,每行 3 个数字,X,A,B。

如果 X=1, 表示第 A 个小朋友分到的糖果必须和第 B 个小朋友分到的糖果一样多;
如果 X=2, 表示第 A 个小朋友分到的糖果必须少于第 B 个小朋友分到的糖果;
如果 X=3, 表示第 A 个小朋友分到的糖果必须不少于第 B 个小朋友分到的糖果;
如果 X=4, 表示第 A 个小朋友分到的糖果必须多于第 B 个小朋友分到的糖果;
如果 X=5, 表示第 A 个小朋友分到的糖果必须不多于第 B 个小朋友分到的糖果;

Output
输出一行,表示老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出 −1。

Hint
N<=10^5

分析:把每个小朋友当成一个结点,每个要求当成一个差分约束,根据要求建图,要把具体的要求转换为数值上的差分,差分值是边权,具体见代码。因为要满足所有小朋友的要求,所以本题要通过求最长路来维护所有结点之间的差分约束关系,求最长路即求最大值。注意题目的隐含条件“每个小朋友都能够分到糖果”,每个小朋友至少要得到1个糖果,需要建立一个超级原点,给每个结点都连一条边权为1的边。其次,注意到有不能满足小朋友们的所有要求的情况,需要对大于和小于要求进行判断(自己不能大于自己、自己不能小于自己),还需要判断负环(泛指永无止境的逻辑)。既然需要判断负环,本题就得使用SPFA算法。
对于负环,举个例子:A要求分到糖果比B多(少);B要求分到糖果比C多(少);C要求分到糖果比A多(少)。可以发现这是一个没有止境的过程,增加(减少)糖果数会无限地进行下去。
另外,需要注意,题目一般默认求从结点1到结点n的路,而起点不同会影响图的遍历进而影响路的寻找,所以给超级原点加边的时候要注意加边的顺序,保证结点1先被访问,否则结果会出错,当时我就因为这个原因导致代码没AC。

最后,附上找负环方法:
1.记录此点被更新了多少次,更新次数大于等于结点数N,就有负环,因为没有负环的情况下,一个点最多能被剩下N-1个点各更新一次,更新次数能大于等于N就说明有点被重复经过重复更新。
2.记录从原点到当前点的路径上经过了多少个点,大于N则有负环,因为一张图才N个点,一条路径上没有重复点经过点数肯定肯定小于等于N。
3.开个数组vis,记录某个点没有入栈,沿着当前点所连边一直DFS下去,直到遇到某个点当前已经入栈了,就找到了负环,因为这意味着一个点在最短(长)路上重复出现。
第一种方法最常用,最好实现,这里我用第一种方法。

已AC代码:

#include <stdio.h>
#include <queue>
#include <string.h>//提供memset()
#include <algorithm>//提供exit()

struct EDGE
{
    
    
    int v,next,val;//到达结点,邻边,边权
}edge[500010];//因为结点数上限是10^5,考虑极限情况
			  //每两个结点之间双向相连,2*2N条(画图验证)
			  //还要加上超级原点到每个结点的边,N条

int head[100010],idx=0;
int n,k;//结点数,条件数
long long int sum=0;//最终答案
int count_visit[100010];//计数结点更新次数
bool visit[100010];//标记结点入队
int d[100010];//松弛操作记录量

inline void add_edge(int x,int y,int val)
{
    
    
    edge[++idx].v=y;
    edge[idx].val=val;
    edge[idx].next=head[x];
    head[x]=idx;
}

void spfa()
{
    
    
    std::queue<int> que;
    que.push(0);//超级原点入队
    visit[0]=true;
    d[0]=0;
    ++count_visit[0];//超级原点被更新一次

    int now;
    while (!que.empty())
    {
    
    
        now=que.front();
        que.pop();
        visit[now]=false;

        if(count_visit[now]>=n)//判断负环
        {
    
    
            printf("-1");
            exit(0);
        }

        for(int i=head[now];~i;i=edge[i].next)
        {
    
    	
        	//寻找最长路,因为要求满足所有小朋友需求的至少值
            if(d[edge[i].v]<d[now]+edge[i].val)
            {
    
    
                d[edge[i].v]=d[now]+edge[i].val;
                ++count_visit[now];//到达结点更新一次
                if(!visit[edge[i].v])
                {
    
    
                    que.push(edge[i].v);
                    visit[edge[i].v]=true;
                }
            }
        }
    }
}

int main() {
    
    
    memset(head,-1,sizeof(head));
    memset(count_visit,0,sizeof(count_visit));
    memset(visit,0,sizeof(visit));
    memset(d,-0x7f7f7f7f,sizeof(d));//找最长路,找最大值,初值赋最小

    scanf("%d%d",&n,&k);
    int x,a,b;
    for(int i=0;i<k;++i)
    {
    
    
        scanf("%d%d%d",&x,&a,&b);
        if(x==1){
    
    //A=B,等价于A>=B,A<=B,进一步可转化为
            add_edge(a,b,0);//A+0<=B
            add_edge(b,a,0);//B+0<=A
        }
        if(x==2){
    
    //A<B,等价于A+1<=B
            if(a==b){
    
    //自己无法小于自己
                printf("-1");
                return 0;
            }
            add_edge(a,b,1);
        }
        if(x==3){
    
    //A>=B,等价于B+0<=A
            add_edge(b,a,0);
        }
        if(x==4){
    
    //A>B,等价于B+1<=A
            if(a==b){
    
    //自己无法大于自己
                printf("-1");
                return 0;
            }
            add_edge(b,a,1);
        }
        if(x==5){
    
    //A<=B,等价于A+0<=B
            add_edge(a,b,0);
        }
    }
    for(int i=n;i>0;--i)//题目未说明一般表示默认寻找从结点1到n的路
    {
    
    
        add_edge(0,i,1);//故超级原点的边要倒着加,保证先访问结点1
    }

    spfa();

    for(int i=1;i<=n;++i)//计算答案
    {
    
    
        sum+=d[i];
    }
    printf("%lld",sum);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_44643644/article/details/108465327
今日推荐