[SDOI2009]晨跑

 原题连接:https://www.luogu.org/problemnew/show/P2153

题目描述

Elaxia最近迷恋上了空手道,他为自己设定了一套健身计划,比如俯卧撑、仰卧起坐等 等,不过到目前为止,他坚持下来的只有晨跑。 现在给出一张学校附近的地图,这张地图中包含N个十字路口和M条街道,Elaxia只能从 一个十字路口跑向另外一个十字路口,街道之间只在十字路口处相交。Elaxia每天从寝室出发 跑到学校,保证寝室编号为1,学校编号为N。 Elaxia的晨跑计划是按周期(包含若干天)进行的,由于他不喜欢走重复的路线,所以 在一个周期内,每天的晨跑路线都不会相交(在十字路口处),寝室和学校不算十字路 口。Elaxia耐力不太好,他希望在一个周期内跑的路程尽量短,但是又希望训练周期包含的天 数尽量长。 除了练空手道,Elaxia其他时间都花在了学习和找MM上面,所有他想请你帮忙为他设计 一套满足他要求的晨跑计划。

存在1n 的边存在。这种情况下,这条边只能走一次。

输入输出格式

输入格式:

 

第一行:两个数N,M。表示十字路口数和街道数。 接下来M行,每行3个数a,b,c,表示路口a和路口b之间有条长度为c的街道(单向)。

 

输出格式:

 

两个数,第一个数为最长周期的天数,第二个数为满足最长天数的条件下最短的路程长 度。

 

输入输出样例

输入样例

7 10
1 2 1
1 3 1
2 4 1
3 4 1
4 5 1
4 6 1
2 5 5
3 6 6
5 7 1
6 7 1
输出样例
2 11

说明

对于30%的数据,N ≤ 20,M ≤ 120。

对于100%的数据,N ≤ 200,M ≤ 20000。

 

 嗯....这道题目要求最短长度,最多天。那很显然跑最小费用最大流就可以了

讲一讲建边qwq

每个十字路口只能经过一次,既然这样,那我们就将每个十字路口拆成两个点 流量设为1,费用为0(为什么,想一想) a->a1(a1表示拆出来的点) qwq,但是源点,汇点不止走一次,所以不用拆点

Code

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N=20000+10;
const int M=6000000;
const int inf=0x7fffffff;
int v[N*4],w[N*4],cst[N*4],nxt[N*4],first[N],dis[N],pre[N],pos[N];  
int cnt=1,n,m,flow,cost,t;
bool inq[N];
struct queue{
    int head,tail,que[M];
    void clear(){head=tail=1;}  //这一行其实没有意义
    void pop(){head++;if(head==M) head=0;}
    void push(int x){que[tail++]=x;if(tail==M) tail=0;}
    int front(){return que[head];}
    bool empty(){return head==tail;}
};
queue q;   //把queue封装在结构体里

void read(int &x){   //读入优化
    x=0;int f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-f;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c-'0');c=getchar();}
    x*=f;
}

void add(int a,int b,int c,int d){  //前向星存边
    v[++cnt]=b;
    w[cnt]=c;
    cst[cnt]=d;
    nxt[cnt]=first[a];
    first[a]=cnt;
}

bool spfa(){     //找最小费用
    for(int i=1;i<=2*n;i++) dis[i]=inf;
    q.clear();
    q.push(1);dis[1]=0;inq[1]=1;
    while(!q.empty()){
        int x=q.front();q.pop();inq[x]=0;
        for(int i=first[x];i;i=nxt[i]){
            if(w[i]&&dis[x]+cst[i]<dis[v[i]]){
                pre[v[i]]=x;pos[v[i]]=i;  //pre:记录前继节点,pos:该节点对应的边
                dis[v[i]]=dis[x]+cst[i];
                if(!inq[v[i]]){
                    q.push(v[i]);
                    inq[v[i]]=1;
                }
            }
        }
    }
    return dis[n]!=inf;  //是否存在一条从源点到汇点的路径
}

int main(){
    read(n);read(m);
    for(int i=1;i<=m;i++){
        int a,b,c;
        read(a);read(b);read(c);
        if(a!=1) a=n+a;  //用拆出来的点连b,1不用拆点
        add(a,b,1,c);add(b,a,0,-c);
    }
    for(int i=2;i<n;i++){add(i,i+n,1,0);add(i+n,i,0,0);}  //拆点
    while(spfa()){
        int f=1;
        flow+=1;cost+=dis[n];
        for(int i=n;i!=1;i=pre[i]){
            w[pos[i]]-=f;
            w[pos[i]^1]+=f;
        }
    }
    printf("%d %d",flow,cost);
    return 0;
}

 

 

posted @ 2018-02-12 21:35  lyf2  阅读(163)  评论(2编辑  收藏  举报