费用流例题

1.k取方格数

一个矩阵格子,从最左上角出发到最右下角,走k次,每个格子上有一个数,走过一次就变为0,问能取到的所有数字之和最大值。

每个点 i 拆成两个点 i 和 i'(左半边与右半边),两点之间连两条边一条容量为 1,费用为 -a[i], 另一条容量为无穷,费用为0

每个点的右半边与相邻点的左半边连起来,容量为无穷(应该是只要比k大就行),费用为0。

从源点到左上角的点连一条边,容量为 k, 费用为0,从右下角到汇点连一条边,容量为k,费用为0,跑一次最小费用最大流

 

2.小改

此时费用变为,该边上流量的平方,比如一条边容量为10,流量为2,则费用为4

思路:原本的算法只对费用与流量成正比的时候有效,现在变成一个凸函数,不能直接使用,则拆边,把一条容量为n的边拆成n条,分别为

容量为1,费用为 1^2 - 0^2

容量为2,费用为 2^2 - 1^2

......

容量为n, 费用为 n^2 - (n-1)^2

 

3.序列选数

给一个长为 n 的序列,从中分别取出1,2,3,...,n/2个不相邻的数,分别和最大,输出

当 只取一个数的时候,很明显答案就是序列中最大的那个数

使用费用流的思路,当我们要取第二个及以后的数的时候,需要允许反悔,则对

i - 1, i, i + 1,三个数,如果我们取了第 i 个数,那么i-1和i+1就不能取,除非反悔,发现 a[i-1]+a[i+1] >= a[i]

那么只需要在取了第i个点后,将这三个点合并成为一个值为 a[i-1]+a[i+1]-a[i]的新点即可

折叠

#include<bits/stdc++.h>
using namespace std;
const int MXN = 100010, inf = 0x3f3f3f3f;
struct Range{
	int l, pre, nxt, v;
	bool operator<(Range x)const{ return v < x.v;}
}r[MXN];
priority_queue<Range> q;
int main(){
	int t, n, ans, cnt;
	scanf("%d", &t);
	while(t--){
		ans = 0;
		while(!q.empty()) q.pop();
		scanf("%d", &n);
		for(int i = 1; i <= n; ++i){
			scanf("%d", &r[i].v);
			r[i].pre = i-1, r[i].nxt = i+1; // 前驱后继
			q.push((Range){i, 0, 0, r[i].v});
		}
		cnt = n/2;
		while(!q.empty() && cnt){
			Range k = q.top();
			q.pop();
			Range &y = r[k.l];
			if(y.v != k.v) continue;     // 被合并的点,此时就不能再选了
			ans += y.v, cnt--;
			printf("%d\n", ans);
			Range &x = r[y.pre], &z = r[y.nxt];
			if(y.pre < 1 && y.nxt > n) continue; // 不符合规定的点
			else if(y.pre == 0) y.v = z.v = -inf, r[z.nxt].pre = 0;  //如果这个点没有前驱或者后继,直接删点
			//没有反悔的可能
			else if(y.nxt > n) x.v = y.v = -inf, r[x.pre].nxt = y.nxt;
			else{
				x.v += z.v - y.v, x.nxt = z.nxt;
				y.v = z.v = -inf;
				r[z.nxt].pre = y.pre;
				k.v = x.v, k.l = y.pre;
				q.push(k);
			}
			// 否则对于三个点, 保留最左边的点,旁边两点删去
		}
	}
    return 0;
}
posted @ 2024-02-28 21:17  是谁不可理喻  阅读(10)  评论(0)    收藏  举报