洛谷P3275 [SCOI2011]糖果(spfa(松弛))
题目地址:https://www.luogu.com.cn/problem/P3275
题目描述
幼儿园里有 N 个小朋友, 老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候,需要满足小朋友们的 KK 个要求。幼儿园的糖果总是有限的,想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。
输入
输入的第一行是两个整数 N,K。接下来 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
作者:孙建钊
出处:http://www.cnblogs.com/sunjianzhao/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。