JZOJ 1090. 【SDOI2009】晨跑

题目

略,luogu上有

解析

一眼费用流
然而怎么建图?
首先我们要挖掘题中的限制条件和性质

  • 一个点只能经过一次
  • 能走的天数最长
  • 满足第二条的条件下走过的路程最短

那么显然是最小费用最大流了
对于后两条,我们发现我们求的最大流就要对应天数,最小费用就要对应路程最短
再联系第一条
一个点只能经过一次,那么我们就可以把它拆开成两个点 (i_1,i_2) 连流量为 (1) 费用为 (0) 的边,表示只能流过一次
然后就要使 (u,v) 间的连边变为 (u_2,v_1,1,w)(v_1,u_2,0,-w)
那么源点 (S) 就是 (1) 拆成的第二个点,汇点 (T) 就是 (n_1)

最后跑一边 (MCMF) 就好了

(Code)

#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
using namespace std;

const int N = 405 , M = 4e4 + 5;
int n , m , pre[N] , edge[N] , dis[N] , flow[N] , h[N] , vis[N] , tot = 1 , Mincost , Maxflow , S , T;
queue<int> Q;

struct node{
	int to , nxt , w , f;
}e[M << 1];

inline void add(int u , int v , int w , int f){e[++tot] = node{v , h[u] , w , f} , h[u] = tot;}

inline int spfa()
{
	memset(dis , 127 , sizeof dis);
	memset(flow , 127 , sizeof flow);
	memset(vis , 0 , sizeof vis);
	dis[S] = 0 , vis[S] = 1 , Q.push(S) , pre[T] = -1;
	while (!Q.empty())
	{
		int now = Q.front();
		Q.pop();
		for(register int i = h[now]; i; i = e[i].nxt)
		{
			int v = e[i].to;
			if (dis[now] + e[i].f < dis[v] && e[i].w)
			{
				dis[v] = dis[now] + e[i].f , pre[v] = now , edge[v] = i , flow[v] = min(flow[now] , e[i].w);
				if (!vis[v]) vis[v] = 1 , Q.push(v);
			}
		}
		vis[now] = 0;
	}
	return pre[T] != -1;
}

inline void MCMF()
{
	while (spfa())
	{
		Maxflow += flow[T];
		Mincost += dis[T] * flow[T];
		int now = T;
		while (now != S)
		{
			e[edge[now]].w -= flow[T];
			e[edge[now] ^ 1].w += flow[T];
			now = pre[now];
		}
	}
}

int main()
{
	scanf("%d%d" , &n , &m);
	for(register int i = 1; i <= n; i++)
	{
		add(i * 2 - 1 , i * 2 , 1 , 0);
		add(i * 2 , i * 2 - 1 , 0 , 0);
	}
	int u , v , f;
	for(register int i = 1; i <= m; i++)
	{
		scanf("%d%d%d" , &u , &v , &f);		
		add(u * 2 , v * 2 - 1 , 1 , f) , add(v * 2 - 1 , u * 2 , 0 , -f);
	}
	S = 2 , T = n * 2 - 1;
	MCMF();
	printf("%d %d
" , Maxflow , Mincost);
}
原文地址:https://www.cnblogs.com/leiyuanze/p/13499216.html