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
#include
#include //提供memset()
#include //提供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
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;
}