【YBT2023寒假Day1 A】孤走暗巷(费用流)

孤走暗巷

题目链接:YBT2023寒假Day1 A

题目大意

给你一个整数序列,你要通过一些操作把它变成单调不降序列。
你有 m 种操作,每次可以选择一个长度为 li 的区间,花费 ci 的代价将其加一或减一(是加一还是减一看给你的操作),问你最小代价,或者输出无解。

思路

首先这个区间加减不难想到通过差分变成某两个位置分别加一减一。
然后发现变成单调不降就是中间的部分都是要非 0 0 0 数。

看到 n , m n,m n,m 都很小,而且 DP 似乎不太可行(因为有两个位置同时修改),考虑费用流。
然后因为两个位置同时修改的都是 1 1 1 的绝对值,而且是相反数,你可以理解为把这个 1 1 1 交给了另一个位置。
那你在思考一开始的正负,会发现正的就是它可以给出的最大数量,负的就是它需要的最小数量。
然后因为两边的正负是无所谓的,也就是它可以给出无限的数量。

那我们就把正的跟源点连,负的跟汇点连,两边就是跟源点连流量无限大,都是费用 0 0 0
然后再模拟给的操作思考会发现是给的流向收到的,然后连边流量无限费用是给出的值。
不过要注意的是它只是限制的区间的长度,所以你可以直接从左往右滑动那个区间,每个都要连边,边数是 O ( n m ) O(nm) O(nm) 级别没问题。

至于判断是否有解,其实就是看是否刘曼,然后因为源点那边有无限流量的边,我们判断汇点那边的边就行。

代码

#include<queue>
#include<cstdio>
#include<iostream>
#define ll long long
#define INF 0x3f3f3f3f3f3f3f3f

using namespace std;

const int N = 250;
const int M = N * N * 20;
const int inf = 1e8;
struct node {
	ll x, w, to, nxt, op;
}e[M];
ll n, m, S, T, tot, le[M], lee[M], KK;
ll dis[M], deg[M], h[N], p[N];
bool in[M];
queue <ll> q;

void add(ll x, ll y, ll z, ll w) {
	e[++KK] = (node){z, w, y, le[x], KK + 1}; le[x] = KK;
	e[++KK] = (node){0, -w, x, le[y], KK - 1}; le[y] = KK;
}

bool SPFA() {
	for (ll i = 0; i <= tot; i++) {
		dis[i] = INF;
		in[i] = 0;
		deg[i] = -1;
		lee[i] = le[i];
	}
	while (!q.empty()) q.pop();
	
	q.push(S);
	dis[S] = 0;
	in[S] = 1;
	deg[S] = 0;
	while (!q.empty()) {
		ll now = q.front();
		q.pop();
		
		for (ll i = le[now]; i; i = e[i].nxt)
			if (e[i].x > 0 && dis[e[i].to] > dis[now] + e[i].w) {
				dis[e[i].to] = dis[now] + e[i].w;
				deg[e[i].to] = deg[now] + 1;
				if (!in[e[i].to]) {
					in[e[i].to] = 1;
					q.push(e[i].to);
				}
			}
		
		in[now] = 0;
	}
	
	if (dis[T] == dis[0]) return 0;
	return 1;
}

ll dfs(ll now, ll sum) {
	if (now == T) return sum;
	in[now] = 1;
	ll go = 0;
	for (ll &i = lee[now]; i; i = e[i].nxt)
		if (!in[e[i].to] && e[i].x > 0 && dis[e[i].to] == dis[now] + e[i].w && deg[e[i].to] == deg[now] + 1) {
			ll this_go = dfs(e[i].to, min(sum - go, e[i].x));
			if (this_go) {
				e[i].x -= this_go;
				e[e[i].op].x += this_go;
				go += this_go;
				if (go == sum) return go;
			}
		}
	if (go == sum) in[now] = 0;
	return go;
}

ll dinic_cost() {
	ll re = 0;
	while (SPFA()) {
		re += dis[T] * dfs(S, INF);
	}
	return re;
}

int main() {
	freopen("seq.in", "r", stdin);
	freopen("seq.out", "w", stdout);
	
	scanf("%lld %lld", &n, &m);
	for (int i = 1; i <= n; i++) scanf("%lld", &h[i]);
	for (int i = 1; i <= n + 1; i++) p[i] = h[i] - h[i - 1];
	
	S = n + 2;
	T = n + 3;
	tot = T;
	
	for (int i = 1; i <= n + 1; i++) {
		if (i == 1 || i == n + 1) add(S, i, inf, 0);
			else if (p[i] > 0) add(S, i, p[i], 0);
				else if (p[i] < 0) add(i, T, -p[i], 0);
	}
	for (int i = 1; i <= m; i++) {
		char op = getchar(); while (op != '+' && op != '-') op = getchar();
		int l, c; scanf("%d %d", &l, &c); if (l == n) continue;
		for (int j = 1; j + l <= n + 1; j++) {
			if (op == '-') add(j, j + l, inf, c);
				else add(j + l, j, inf, c);
		}
	}
	
	ll ans = dinic_cost();
	for (int i = le[T]; i; i = e[i].nxt) {
		if (e[e[i].op].x) {
			printf("-1"); return 0;
		}
	}
	printf("%lld", ans);
	
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值