codevs2306晨跑(SDOI2009)

CodeVS 2306晨跑 解题报告 最小费用最大流

题目描述 Description

Elaxia最近迷恋上了空手道,他为自己设定了一套健身计划,比如俯卧撑、仰卧起坐等等,不过到目前为止,他坚持下来的只有晨跑。

现在给出一张学校附近的地图,这张地图中包含N个十字路口和M条街道,Elaxia只能从一个十字路口跑向另外一个十字路口,街道之间只在十字路口处相交。Elaxia每天从寝室出发跑到学校,保证寝室编号为1,学校编号为N。

Elaxia的晨跑计划是按周期(包含若干天)进行的,由于他不喜欢走重复的路线,所以在一个周期内,每天的晨跑路线都不会相交(在十字路口处),寝室和学校不算十字路口。Elaxia耐力不太好,他希望在一个周期内跑的路程尽量短,但是又希望训练周期包含的天数尽量长。

除了练空手道,Elaxia其他时间都花在了学习和找MM上面,所有他想请你帮忙为他设计一套满足他要求的晨跑计划。

输入描述 Input Description

第一行:两个数N,M。表示十字路口数和街道数。

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

输出描述 Output Description

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

样例输入 Sample Input

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

样例输出 Sample Output

2 11

数据范围及提示 Data Size & Hint

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

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

分析

      拆点+最小费用最大流
      把每个点都拆成两个点,i和n+i,连一条从i到n+i的边,容量为1,费用为0,对于实际图中的一条边x to y,我们在网络流图中连边x+n to y,权值与实际图中相同,容量设为1。
      跑一遍最小费用最大流(我用E-K算法),第一问输出最大流flow,第二问输出最小费用cost。
      简单说一下E-K算法求最小费用最大流:用SPFA在残余网络中找到一条从s到t的最短路,然后用这条路径进行增广(统计答案、加反向边),两者循环往复,直到spfa无法找到一条增广路。
      一些小地方:我当时程序写错了一点小地方,陷入了永无休止的死循环,我便怀疑在残余网络中可能存在负权回路,但是不难发现,当进行某次spfa之前,所有的负权路径都是之前几次的最短路,所以当前存在的路径的长度一定大于等于这些路径,所以不会出现负权回路。

代码

//CodeVS 2306晨跑 最小费用最大流 
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#define maxn 205
#define maxm 20005
using namespace std;
int d[maxn<<1], head[maxn<<1], next[maxm<<2], v[maxm<<2], w[maxm<<2],
	c[maxm<<2], N, M, tot=1, pre[maxn<<1], vis[maxn<<1], ans, e[maxn<<1],
	cnt;
queue<int> q;
void add(int x, int y, int z, int cc)
{v[++tot]=y;w[tot]=z;c[tot]=cc;next[tot]=head[x];head[x]=tot;}
bool spfa()
{
	int p, x, t;
	memset(d,0x3f,sizeof(d));
	memset(vis,0,sizeof(vis));
	while(!q.empty())q.pop();
	d[N+1]=0;vis[N+1]=true;q.push(N+1);
	while(!q.empty())
	{
		x=q.front();q.pop();
		for(p=head[x];p;p=next[p])
		{
			if( c[p] && d[x]+w[p]<d[t=v[p]])
			{
				d[t]=d[x]+w[p];
				pre[t]=x,e[t]=p;
				if(!vis[t])vis[t]=true,q.push(t);
			}
		}
		vis[x]=false;
	}
	return d[N]!=0x3f3f3f3f;
}
void augment()
{
	int t=N, p;
	while(t!=N+1)
	{
		p=e[t];
		c[p]--;c[p^1]++;
		t=pre[t];
	}
	ans+=d[N];
}
int main()
{
	int i, j, x, y, z;
	scanf("%d%d",&N,&M);
	for(i=1;i<=N;i++)
		add(i,N+i,0,1),add(N+i,i,0,0);
	for(i=1;i<=M;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		add(x+N,y,z,1);add(y,x+N,-z,0);
	}
	while(spfa())cnt++,augment();
	printf("%d %d\n",cnt,ans);
	return 0;
}

测试点#run0.in  结果:    内存使用量:  256kB     时间使用量:  1ms     
测试点#run1.in 结果: 内存使用量: 256kB 时间使用量: 1ms
测试点#run2.in 结果: 内存使用量: 256kB 时间使用量: 1ms
测试点#run3.in 结果: 内存使用量: 256kB 时间使用量: 1ms
测试点#run4.in 结果: 内存使用量: 492kB 时间使用量: 44ms
测试点#run5.in 结果: 内存使用量: 364kB 时间使用量: 10ms
测试点#run6.in 结果: 内存使用量: 492kB 时间使用量: 45ms
测试点#run7.in 结果: 内存使用量: 744kB 时间使用量: 143ms
测试点#run8.in 结果: 内存使用量: 748kB 时间使用量: 97ms
测试点#run9.in 结果: 内存使用量: 1132kB 时间使用量: 386ms

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值