bzoj1266 AHOI2006 上学路线 最短路+最小割

17 篇文章 0 订阅
12 篇文章 0 订阅

题意:

给出一个n个点m条边的无向图,每一条边有长度和去掉的代价,先求1号点到n号点的长度最短路,在求去掉最小代价的一些边,使1到n的长度最短路变大

题解:

求最短路直接SPFA就行了,然后在最短路径图上跑一个代价的最小割就行了。

最短路劲图就是图上的每一条边都在某条(些)最短路上,建图的方法是判断一条路径两个端点x,y是否满足dis[x]==dis[y]+当前路径的长度。

在这个图上跑最小割就行,因为不在最短路径图上的边去掉也不会对任意一条最短路有影响,所以那些边就不用管了。

下面是代码

#include <bits/stdc++.h>
using namespace std;
 
int n,m,dis[500001],head[500001],cnt,hed[500001];
int inq[500001],q[500001],h,t,res,dep[500001];
struct node
{
    int from,to,dis,c,next,other;
}ji[500000],a[500001],b[500001];
void add(int from,int to,int dis)
{
    a[++cnt].to=to;
    a[cnt].dis=dis;
    a[cnt].next=head[from];
    head[from]=cnt;
}
void ins(int from,int to,int c)
{
    int k1,k2;
    ++cnt;
    k1=cnt;
    b[cnt].to=to;
    b[cnt].c=c;
    b[cnt].next=hed[from];
    hed[from]=cnt;
    ++cnt;
    k2=cnt;
    b[cnt].to=from;
    b[cnt].c=0;
    b[cnt].next=hed[to];
    hed[to]=cnt;
    b[k1].other=k2;
    b[k2].other=k1;
}
void spfa()
{
    memset(dis,127/3,sizeof(dis));
    q[1]=1;
    dis[1]=0;
    h=1;t=2;
    while(h!=t)
    {
        int x=q[h];
        inq[x]=0;
        for(int i=head[x];i;i=a[i].next)
        {
            int y=a[i].to;
            if(dis[y]>dis[x]+a[i].dis)
            {
                dis[y]=dis[x]+a[i].dis;
                if(!inq[y])
                {
                    q[t++]=y;
                    inq[y]=1;
                }
            }
        }
        h++;
    }
}
int bfs()
{
    memset(dep,0,sizeof(dep));
    q[1]=1;
    h=1;t=2;
    dep[1]=1;
    while(h!=t)
    {
        int x=q[h];
        for(int i=hed[x];i;i=b[i].next)
        {
            int y=b[i].to;
            if(b[i].c&&dep[y]==0)
            {
                dep[y]=dep[x]+1;
                q[t++]=y;
            }
        }
        h++;
    }
    if(dep[n]>0)
    return 1;
    else
    return 0;
}
int flow(int x,int f)
{
    if(x==n)
    return f;
    int s=0,t;
    for(int i=hed[x];i;i=b[i].next)
    {
        int y=b[i].to;
        if(b[i].c&&dep[y]==dep[x]+1&&s<f)
        {
            s+=(t=flow(y,min(b[i].c,f-s)));
            b[i].c-=t;
            b[b[i].other].c+=t;
        }
    }
    if(s==0)
    dep[x]=0;
    return s;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int u,v,x,y;
        scanf("%d%d%d%d",&ji[i].from,&ji[i].to,&ji[i].dis,&ji[i].c);
        add(ji[i].from,ji[i].to,ji[i].dis);
        add(ji[i].to,ji[i].from,ji[i].dis);
    }
    spfa();
    printf("%d\n",dis[n]);
    cnt=0;  
    for(int i=1;i<=m;i++)
    {
        if(dis[ji[i].from]==dis[ji[i].to]+ji[i].dis)
        ins(ji[i].to,ji[i].from,ji[i].c);    
        if(dis[ji[i].to]==dis[ji[i].from]+ji[i].dis)
        ins(ji[i].from,ji[i].to,ji[i].c);      
    }
    while(bfs())       
    res+=flow(1,2e9);
    printf("%d\n",res);
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值