P2469 [SDOI2010]星际竞速 解题报告

P2469 [SDOI2010]星际竞速 解题报告

这可能是我人生中的第一道费用流(模板除外)

题目链接

https://www.luogu.com.cn/problem/P2469

题目大意

(稍加转化)

给定一个带权有向无环图,要求 “经过” 恰好 一次每个点。

这里的 “经过” 是一种路径覆盖操作,具体地说:

  1. 选定一个 出发点 i i i,这需要花费你 额外 a i a_i ai 钱。
  2. 走过一条以 i i i 为出发点的路径(包括 i i i ),路经权值和为你要花的钱。

最少要花多少钱?

  • 点数 n ≤ 800 n\le 800 n800, 边数 m ≤ 15000 m\le 15000 m15000

解题思路

看到这个问题,我们会想到最大流/二分图中的最小路径覆盖问题。于是我们套路性地拆点,套路性地连边,套路性地定义一条流代表两个点之间的“路径合并”。由于边带权,可以想到可能是一道费用流。

解法1

连边为:

s → u s\to u su,容量1费用0。

u → v ′ u\to v' uv,容量1费用为边权。

v ′ → t v'\to t vt,容量1费用0。

考虑 a i a_i ai 怎么表现出来。

我们发现,进入每个点有两种情况,而且必须二选一:

  1. 从另外一个点进入。
  2. 从全局进入。

那么只需要连边 s → v ′ s\to v' sv,容量1费用 a i a_i ai,它与许许多多的 u → v ′ u\to v' uv 边“并联”。由于 v ′ → t v'\to t vt 的边容量为1,这代表着只有一条边可以被选择,正好符合定义。

稍加思虑发现,只需要求 最小费用最大流 即可。

解法2

同样考虑 a i a_i ai 怎么表现出来。

我们考虑图上没有 “路径合并” 的时的状态。这时相当于每个路径仅覆盖一个点,总代价为 ∑ a i \sum a_i ai

为了更优,我们 在这基础上 进行路径合并。如果 ( u , v ) (u,v) (u,v) 被选上,那么我们需要多出 w ( u , v ) w(u,v) w(u,v) 的权值,但会减少 a v a_v av 的权值。于是可以这样建图:

s → u s\to u su,容量1费用0。

u → v ′ u\to v' uv,容量1费用为边权。

v ′ → t v'\to t vt,容量1费用 − a v -a_{v} av

在上面跑 最小费用可行流,最后答案就是 ∑ a i + m i n c o s t \sum a_i +\mathrm{mincost} ai+mincost

代码

解法1

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
char In[1 << 20], *ss = In, *tt = In;
#define getchar() (ss == tt && (tt = (ss = In) + fread(In, 1, 1 << 20, stdin), ss == tt) ? EOF : *ss++)
ll read() {
	ll x = 0, f = 1; char ch = getchar();
	for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + int(ch - '0');
	return x * f;
}
const int MAXN = 805;
const int MAXM = 15005;
const int MAXNODE = 1705;
const int MAXEDGE = 50005;
const int INF = 0x3f3f3f3f;
int n, m, s, t, a[MAXN], ver[MAXEDGE], nxt[MAXEDGE], edg[MAXEDGE], cap[MAXEDGE], head[MAXNODE], cnt = 1;

void addedge(int u, int v, int w, int c) {
	ver[++cnt] = v; nxt[cnt] = head[u]; edg[cnt] = w; cap[cnt] = c; head[u] = cnt;
	ver[++cnt] = u; nxt[cnt] = head[v]; edg[cnt] = 0; cap[cnt] = -c; head[v] = cnt;
}
//s:2*n+1, t:2*n+2
//u:1~n
//u':n+1~n+n

int inq[MAXNODE], dist[MAXNODE], incf[MAXNODE], pre[MAXNODE], maxflow, mincost;
bool spfa() {
	memset(inq, 0x00, sizeof inq); memset(dist, 0x3f, sizeof dist);
	queue<int> que;
	que.push(s); inq[s] = 1; dist[s] = 0; incf[s] = INF;
	while(que.size()) {
		int u = que.front(); que.pop(); inq[u] = 0;
		for(int i = head[u]; i; i = nxt[i]) if(edg[i]) {
			int v = ver[i];
			if(dist[v] > dist[u] + cap[i]) {
				dist[v] = dist[u] + cap[i];
				incf[v] = min(incf[u], edg[i]);
				pre[v] = i;
				if(!inq[v]) que.push(v), inq[v] = 1;
			}
		}
	}
	return dist[t] != INF;
}
void upd() {
	int now = t;
	while(now != s) {
		int p = pre[now];
		edg[p] -= incf[t]; edg[p^1] += incf[t];
		now = ver[p^1];
	}
	maxflow += incf[t]; mincost += incf[t] * dist[t];
}
void MCMF() {
	while(spfa()) upd();
}
int main() {
	n = read(), m = read();
	for(int i = 1; i <= n; i++) a[i] = read();
	s = n+n+1, t = n+n+2;
	for(int i = 1; i <= n; i++) addedge(s, i, 1, 0);
	for(int i = n+1; i <= n+n; i++) addedge(i, t, 1, 0);
	for(int i = 1; i <= n; i++) addedge(s, i+n, 1, a[i]);
	for(int i = 1; i <= m; i++) {
		int u = read(), v = read(), w = read();
		if(u > v) swap(u, v);
		addedge(u, v+n, 1, w);
	}
	MCMF();
	printf("%d\n", mincost);
	return 0;
}

解法2

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
char In[1 << 20], *ss = In, *tt = In;
#define getchar() (ss == tt && (tt = (ss = In) + fread(In, 1, 1 << 20, stdin), ss == tt) ? EOF : *ss++)
ll read() {
	ll x = 0, f = 1; char ch = getchar();
	for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + int(ch - '0');
	return x * f;
}
const int MAXN = 805, MAXM = 15005;
const int MAXNODE = 17005, MAXEDGE = 40005;
const int INF = 0x3f3f3f3f;
int n, m, s, t, ver[MAXEDGE], nxt[MAXEDGE], edg[MAXEDGE], cap[MAXEDGE], head[MAXNODE], cnt = 1, ans;
void addedge(int u, int v, int w, int c) {
	ver[++cnt] = v; edg[cnt] = w; cap[cnt] = c; nxt[cnt] = head[u]; head[u] = cnt;
	ver[++cnt] = u; edg[cnt] = 0; cap[cnt] = -c; nxt[cnt] = head[v]; head[v] = cnt;
}
int ansflow, mincost, inq[MAXNODE], dist[MAXNODE], incf[MAXNODE], pre[MAXNODE]; 
bool spfa() {
	memset(dist, 0x3f, sizeof dist); memset(inq, 0x00, sizeof inq);
	queue<int> que;
	dist[s] = 0; inq[s] = 1;
	que.push(s);
	incf[s] = INF;
	while(que.size()) {
		int u = que.front(); que.pop(); inq[u] = 0;
		for(int i = head[u]; i; i = nxt[i]) if(edg[i]) {
			int v = ver[i];
			if(dist[v] > dist[u] + cap[i]) {
				dist[v] = dist[u] + cap[i];
				if(!inq[v]) que.push(v), inq[v] = 1;
				pre[v] = i; incf[v] = min(incf[u], edg[i]);
			}
		}
	}
	return dist[t] < 0;
}
void MCMF() {
	while(spfa()) {
		int now = t;
		ansflow += incf[t]; mincost += incf[t] * dist[t];
		while(now != s) {
			int p = pre[now];
			edg[p] -= incf[t]; edg[p^1] += incf[t];
			now = ver[p^1];
		}
	}
}


int main() {
	n = read(); m = read();
	s = n+n+1, t = n+n+2;
	for(int i = 1; i <= n; i++) {
		int c = read();
		addedge(s, i, 1, 0);
		addedge(i+n, t, 1, -c);
		ans += c;
	}
	for(int i = 1; i <= m; i++) {
		int u = read(), v = read(), c = read();
		if(u > v) swap(u, v);
		addedge(u, v+n, 1, c);
	}
	MCMF();
	printf("%d\n", ans + mincost);

	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日居月诸Rijuyuezhu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值