洛谷P3275 [SCOI2011]糖果(spfa(松弛))

题目地址https://www.luogu.com.cn/problem/P3275

题目描述

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

输入

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

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

输出

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

题解:对于这种只给出两个结点之间的关系的话,可以使用spfa的松弛操作。x=1,其实就是a<=b&&a>=b,所以在a,b之间需要加两条有向边,分别是a到b和b到a,并且权值为0;x=2,在a到b加一条有向边,权值为1;x=3,表示a>=b,在b到a之间加一条权值为0的有向边;x=4,表示a>b,在b到a之间加一条权值为1的有向边;x=5,表示a<=b,在a到b之间加一条权值为0的有向边。另外,需要满足每个点的权值大于等于1,所以需要一个超级源点s(可以为结点0),加n条s到i(i从1到n)权值为1的有向边。

这个时候就可以使用松弛操作,找到到结点i的路径上最长路。只要可以理解这个最长路就可以了。

AC代码:

#include<bits/stdc++.h>
using namespace std;
#define P pair<int,int>
const int N=1e5+10;
const int M=1e5+10;
struct st{
    int from,to,dis,next;
}edge[M*3];
int head[N],cnt=0,n,k,dis[N],num[N],flag=0;
void addedge(int u,int v,int w){
    cnt++;
    edge[cnt].from=u;
    edge[cnt].to=v;
    edge[cnt].dis=w;
    edge[cnt].next=head[u];
    head[u]=cnt;
    return ;
}
void spfa(int s){
    if(flag==1) return ;
    int vis[N]={0};
    memset(dis,0,sizeof(dis));
    memset(num,0,sizeof(num));
    queue<int>q;
    q.push(s);
    dis[s]=0;
    vis[s]=1;
    while(!q.empty()){
        int pa=q.front();q.pop();
        vis[pa]=0;
        num[pa]++;
        if(num[pa]==n){
            flag=1;
            return ;
        }
        for(int i=head[pa];i!=-1;i=edge[i].next){
            int c=edge[i].to,d=edge[i].dis;
            if(dis[c]<dis[pa]+d){
                dis[c]=dis[pa]+d;
                if(vis[c]==0){
                    vis[c]=1;
                    q.push(c);
                }
            }
        }
    }
}

int main(){
    cin>>n>>k;
    flag=0;
    memset(head,-1,sizeof(head));
    for(int i=1,u,v,x;i<=k;i++){
        scanf("%d%d%d",&x,&u,&v);
        if((x==2||x==4)&&u==v) flag=1; 
        switch(x){
            case 1:addedge(u,v,0),addedge(v,u,0);break;
            case 2:addedge(u,v,1);break;
            case 3:addedge(v,u,0);break;
            case 4:addedge(v,u,1);break;
            case 5:addedge(u,v,0);break;
        }
    }
    for(int i=n;i;i--){
        addedge(0,i,1);
    }
    if(flag==1){
        cout<<"-1";
        return 0;
    }
    spfa(0);
    long long int ans=0;
    for(int i=1;i<=n;i++)    ans+=dis[i];
    if(flag==1) cout<<"-1";
    else cout<<ans<<endl;
    return 0; 
}

/*
9 8
2 1 2
2 2 3
2 3 4
2 4 5
4 6 5
2 6 7
2 7 8
2 8 9
*/
/*
6 5
2 1 2
2 2 3
2 3 4
2 4 5
4 6 5
*/

注:不过这里有个超级坑的测试点,如果使用的链式前向星,那么假如超级源点的n条有向边时,必须从n到1添加;如果使用的是vector,那么添加超级源点的n条有向边时,必须从1到n添加。否则会超时,会被卡,在那个超级气人的测试点中,spfa会成为O(VE)的复杂度,简直是超级坑。这个测试点的数据如代码下方我给出的这样的格式,不过测试点中的n,k非常大。所以可以不使用spfa就尽量不使用spfa,毕竟这个算法的复杂度并不稳定。但是对于需要松弛操作的题而言,spfa又是比较简单的方法。

写于:2020/8/14 22:51

posted @ 2020-08-14 22:52  白菜茄子  阅读(182)  评论(0编辑  收藏  举报