bzoj1927 [Sdoi2010]星际竞速

题目链接:bzoj1927
题目大意:
有N颗行星和M条双向星际航路。你有两种移动模式:高速航行模式和能力爆发模式。爆发模式是可以在任意时刻任意跳的。而高速航行只能从编号小的到编号大的走。问在一个路线之外的不知名行星出发,经过每个点恰好一次的最少时间。

题解:
最小费用最大流
拆点,一个表示入的一个表示出的。

  • Si ,流量为1,费用为0
  • iT ,流量为1,费用为0
  • Si ,流量为1,费用为 爆发所需要的时间
  • ij,ij ,流量为1,费用为 高速航行所需要的时间

举个栗子吧:样例的构图如下
这里写图片描述
【。。有点丑,棕色的表示流量,灰蓝的表示费用。】

i 的出边表示经过i的后走的, i 的入边表示从那里到 i 的。因为每个点经过恰好一次,所以流量为1。

/**************************************************************
    Problem: 1927
    User: 564415605
    Language: C++
    Result: Accepted
    Time:4256 ms
    Memory:2508 kb
****************************************************************/

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<queue>
#include<iostream>
#include<algorithm>
using namespace std;
#define maxn 2010
#define maxm 15010

const int inf=1e9;
struct node
{
    int y,f,ot,next,c;
}a[maxm*4];int len,first[maxn];
bool vis[maxn];int d[maxn];
int S,T,flow[maxn],pre[maxn],l[maxn];
int mymin(int x,int y){return (x<y)?x:y;}
void ins(int x,int y,int c,int f)
{
    len++;int n1=len;a[len].y=y;a[len].c=c;
    a[len].f=f;a[len].next=first[x];first[x]=len;
    len++;int n2=len;a[len].y=x;a[len].c=-c;
    a[len].f=0;a[len].next=first[y];first[y]=len;
    a[n1].ot=n2;a[n2].ot=n1;
}
queue<int> q;
int spfa()
{
    for (int i=1;i<=T;i++) d[i]=inf,vis[i]=false,pre[i]=-1;
    q.push(S);d[S]=0;flow[S]=inf;vis[S]=true;
    while (!q.empty())
    {
        int x=q.front();q.pop();
        for (int k=first[x];k!=-1;k=a[k].next)
        {
            int y=a[k].y;
            if (a[k].f<=0) continue; 
            if (d[y]>d[x]+a[k].c)
            {
                d[y]=d[x]+a[k].c;
                flow[y]=mymin(flow[x],a[k].f);
                pre[y]=x;l[y]=k;
                if (!vis[y])
                {
                    vis[y]=true;
                    q.push(y);
                }
            }
        }vis[x]=false;
    }
    if (pre[T]==-1) return 0;
    return flow[T];
}
int MCMF()
{
    int delta,ret=0;
    while (delta=spfa())
    {
        int x=T;ret+=(int)delta*d[T];
        while (x!=S)
        {
            a[l[x]].f-=delta;
            a[a[l[x]].ot].f+=delta;
            x=pre[x];
        }
    }
    return ret;
}
int main()
{
    int n,m,i,u,v,w,ans;
    scanf("%d%d",&n,&m);
    len=0;memset(first,-1,sizeof(first));
    S=n*2+1;T=S+1;
    for (i=1;i<=n;i++)
    {
        scanf("%d",&w);
        ins(S,i*2,w,1);
        ins(S,i*2-1,0,1);
        ins(i*2,T,0,1);
    }
    for (i=1;i<=m;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        if (u>v) swap(u,v);
        ins(u*2-1,v*2,w,1);
    }
    printf("%d\n",MCMF());
    return 0;
}

还有一种方式会快很多。
先假设全都爆发。那么就要sum=ni=1Ai这么多的时间。
于是构图就变成了:

  • Si ,流量为1,费用为0
  • iT ,流量为1,费用为0
  • ij,ij ,流量为1,费用为 高速航行所需要的时间- Aj
    可以说是在找那些点走高速会快点 以缩小当前花费吧。如果到 T 所花的费用已经要大于0了,就直接break就好了。

这么做快了一倍啊。

/**************************************************************
    Problem: 1927
    User: 564415605
    Language: C++
    Result: Accepted
    Time:1992 ms
    Memory:2124 kb
****************************************************************/
 
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<queue>
#include<iostream>
#include<algorithm>
using namespace std;
#define maxn 2010
#define maxm 40010
 
const int inf=1e9;
struct node
{
    int y,f,ot,next,c;
}a[maxm];int len,first[maxn];
bool vis[maxn];int d[maxn],p[maxn];
int S,T,flow[maxn],pre[maxn],l[maxn];
int mymin(int x,int y){return (x<y)?x:y;}
void ins(int x,int y,int c,int f)
{
    len++;int n1=len;a[len].y=y;a[len].c=c;
    a[len].f=f;a[len].next=first[x];first[x]=len;
    len++;int n2=len;a[len].y=x;a[len].c=-c;
    a[len].f=0;a[len].next=first[y];first[y]=len;
    a[n1].ot=n2;a[n2].ot=n1;
}
queue<int> q;
int spfa()
{
    for (int i=1;i<=T;i++) d[i]=inf,vis[i]=false,pre[i]=-1;
    q.push(S);d[S]=0;flow[S]=inf;vis[S]=true;
    while (!q.empty())
    {
        int x=q.front();q.pop();
        for (int k=first[x];k!=-1;k=a[k].next)
        {
            int y=a[k].y;
            if (a[k].f<=0) continue; 
            if (d[y]>d[x]+a[k].c)
            {
                d[y]=d[x]+a[k].c;
                flow[y]=mymin(flow[x],a[k].f);
                pre[y]=x;l[y]=k;
                if (!vis[y])
                {
                    vis[y]=true;
                    q.push(y);
                }
            }
        }vis[x]=false;
    }
    if (pre[T]==-1) return 0;
    return flow[T];
}
int MCMF()
{
    int delta,ret=0;
    while (delta=spfa())
    {
        if (d[T]>0) break;
        int x=T;ret+=(int)delta*d[T];
        while (x!=S)
        {
            a[l[x]].f-=delta;
            a[a[l[x]].ot].f+=delta;
            x=pre[x];
        }
    }
    return ret;
}
int main()
{
    int n,m,i,u,v,w,sum=0;
    scanf("%d%d",&n,&m);
    len=0;memset(first,-1,sizeof(first));
    S=n*2+1;T=S+1;
    for (i=1;i<=n;i++)
    {
        scanf("%d",&p[i]);
        sum+=p[i];
        ins(S,i*2-1,0,1);
        ins(i*2,T,0,1);
    }
    for (i=1;i<=m;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        if (u>v) swap(u,v);
        ins(u*2-1,v*2,w-p[v],1);
    }
    printf("%d\n",sum+MCMF());
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值