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

来源:互联网 发布:移动网络电视好吗? 编辑:程序博客网 时间:2024/05/23 13:16

题意:
给定一个有向图,每条边都有一个权值,每次你可以选择一个结点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 0x3f3f3f3fconst 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;}
原创粉丝点击