UVA 11478 Halum 二分+差分约束+SPFA

本文介绍了一道关于有向图的差分约束问题的解决思路,通过使用二分法来寻找使得所有边权值非负且最大的最小值。文章详细解释了如何构建差分约束系统,并通过SPFA算法验证假设的最小值是否可行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:
给定一个有向图,每条边都有一个权值,每次你可以选择一个结点v和整数d,把所有以v为终点的边权值减少d,把所有以v为起点的边权值增加d,最后要让所有的边权值非负且最大,并输出最小值最大化。
题解:
这道差分约束题可以这样想,因为要找最小中的最大,而又没给值我们进行操作,让我们自由发挥,那么我们是不是可以想到用在茫茫数海中寻找一个符合条件的算法~二分法,找到答案,因为最终我们会找到一条最短路(可能有负环’),我们可以用二分法假设找到了一个答案,将它放进差分约束系统中验证是否正确,对每个点的操作是可以叠加的,我们不妨设sum[i]是对i点的所有操作之和,得出sum[i] - sum[j] + w(i,j)>=0(sum[j]表示的是i的终点j的加和,因为j点加d的话,以j为终点的边肯定会减去d),又因为我们要找到整个图中最小值的最大化所以我们用二分找到答案ans,放进去差分约束系统中,得到公式:sum[i] - sum[j] + w(i,j)>=ans,那么问题就变成了是否可以让操作完毕后每条边的权值均不小于ans。那么这道题分三种情况,第一种放max+1进差分系统看是否满足有解(有解的表现为非负环,无解的表现为负环)至于为什么,请看下面的例子,比如(1->2,5),(2->3,2),(3->4,1),大家觉得这个图的最小边可以无限大吗?可以!因为不是再怎么弄都没办法构成负环,我们可以在(1->2,1)这条边上加上无穷大,因为没有以1为终点的边存在,所以可以任意加,然后后面两条边也可以加上一些值(想象一下,如果1到2这条边无穷大,然后2到3加上1000,根据题意,还要1到2还要减去1000,然后因为1到2是无穷大的所以不用管了,最后3到4的边加上5使最后结果可以满足最小的边长度可以变成max +1,同时2到3的边减去5,但是没影响,因为1002减去5还是比max+1大,所以可以得到无穷大的值。而第二种情况就是没法得到最小值最大大于0的情况,我们可以用1来作为无解的边界。如果是负环的时候就说明无解,否则有解。如果上面两种情况都不出现就看第三种情况,二分找到最小值最大化。
题外话:这道题让我感觉到以前学二分的时候没学清楚到底哪些点要还是不要,到底是l=mid,还是l=mid+1,又或者是r=mid,还是r=mid-1,前面WA的让我怀疑人生,都开始感觉这道题是不是这样做的,dalao有没有说错,说到底还是自己太菜了,while(r>l)没加上=号,ORZ,有空回去再学一下二分的取值。

#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
const int MAXN=500+7;
const int MAXM=2700+7;
struct node
{
    int to,next,w;//其中edge[i].to表示第i条边的终点,edge[i].next表示与第i条边同起点的下一条边的存储位置,edge[i].w为边权值.
}Edge[MAXM];//Edge保持m条边的个数 
int head[MAXM];
int dis[MAXN];
int num[MAXN];
bool vis[MAXN];
int n,m,tot;//head和dis保持n个点 
void add_edge(int a,int b,int c)
{
    Edge[++tot].to=b;
    Edge[tot].w=c;
    Edge[tot].next=head[a];
    head[a]=tot;
}             
bool SPFA()
{
    queue<int>q;
    int k,to,w;
    memset(num,0,sizeof(num));
    memset(vis,false,sizeof(vis));
    for(int i=1;i<=n;i++)
    {
        dis[i]=0;
        num[i]=0;
        vis[i]=true;
        q.push(i);
    } 
    while(!q.empty())
    {
        k=q.front();
        q.pop();
        vis[k]=0;//弹出队列并取消标记
        for(int i=head[k];i!=-1;i=Edge[i].next)
        {
            to=Edge[i].to;
            w=Edge[i].w;
            if(dis[k]+w<dis[to])
            {
                dis[to]=dis[k]+w;
                if(!vis[to])//判断这个点是否在队列里面,如果不在加入队列 
                {
                    vis[to]=true;
                    q.push(to);
                    num[to]++;
                    if(num[to]>n)//判断是否成环 
                    return false;
                }
            }
        }
    }
    return true;
}
bool solve(int x)
{
//  for(int i=1;i<=n;i++) 
//  for(int j=head[i];j!=-1;j=Edge[j].next)
//  Edge[j].w-=x;
//  bool flag=SPFA();
//  for(int i=1;i<=n;i++) 
//  for(int j=head[i];j!=-1;j=Edge[j].next)
//  Edge[j].w+=x;   
//  return flag;
//  上面那种也可以AC,下面这种也可以AC。 
    for(int i=1;i<=tot;i++)
    Edge[i].w-=x;
    bool flag=SPFA();
    for(int i=1;i<=tot;i++)
    Edge[i].w+=x;
    return flag;
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        memset(head,-1,sizeof(head));
        tot=0;
        int MAX=0,ans=1;
        for(int i=1;i<=m;i++)
        {
            int x,y,w;
            scanf("%d%d%d",&x,&y,&w);
            add_edge(x,y,w);
            MAX=max(MAX,w);
        }
        if(solve(MAX+1))
        printf("Infinite\n");
        else if(!solve(1))
        printf("No Solution\n");
        else
        {
            int l=1,r=MAX;
            while(r>=l)//加上=号就过了。。。晕 
            {
                int mid=(r+l)/2;
                if(solve(mid))
                {
                    l=mid+1;
                    ans=mid;
                }
                else
                r=mid-1;
            }
            printf("%d\n",ans);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值