F. Almost Permutation -最小费用最大流

F. Almost Permutation

https://codeforces.ml/problemset/problem/863/F

题意

有一个长度为n的数组,其中的每个数都在1~n范围内,给q个限制(\(1<=n<=50, 0<=q<=100\)
限制有两种类型:
1 l r v 数组[l, r]区间的数最小不小于v
2 l r v 数组[l, r]区间的数最大不大于v
一个数组的花费是 \(\sum_i=1^n cnt[i]^2\) cnt[i]为i在数组中出现的个数
求最小花费 如果不存在这样的数组输出-1.

思路

每个位置我们都要选一个数 且这个数有限制
我们可以把一个位置上可选的数的区间求出来
根据位置与数字的匹配 跑网络流
但是花费出现数量的平方 不能直接处理 我们可以利用数学规律来化解
对于每个数 有i个 即花费\(i^2\) 而等差数列1 3 5 7...i*2-1 的前缀和即该位平方
那我们只要将每个数和源点建立 n条边 流量为1 花费分别为 1 3 5...即可。
然后对于每个位置和可以填的数之间建一条流量为0 花费为1 的边
每个位置再和汇点建一条流量为0 花费为1 的边即可

#include<bits/stdc++.h>
#include<array>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define ll long long
#define int ll

const int N = 3e5 + 5;
const int M = 3e5 + 5;
const ll inf = 0x3f3f3f3f3f3f;

int n, m, l, flow, cost;
int s, t, vtot;
int head[N], vis[N], pre[N];
int dis[N], etot;
pair<int, int>p[N];
struct edge {
	int v, nxt;
	int f;
	int c;
}e[M * 2];
array<int, 4>E[N];

void addedge(int u, int v, int f, int c) {
	e[etot] = { v, head[u], f, c }; head[u] = etot++;
	e[etot] = { u, head[v], 0, -c }; head[v] = etot++;
}

//s到t是否还有路径
bool spfa() {
	for (int i = 1; i <= vtot; i++) {
		dis[i] = inf / 2;
		vis[i] = false;
		pre[i] = -1;
	}
	dis[s] = 0;
	vis[s] = true;
	queue<int>q;
	q.push(s);
	while (!q.empty()) {
		int u = q.front();
		for (int i = head[u]; ~i; i = e[i].nxt) {
			int v = e[i].v;
			if (e[i].f && dis[v] > dis[u] + e[i].c) {
				dis[v] = dis[u] + e[i].c;
				pre[v] = i;
				if (!vis[v]) {
					vis[v] = 1;
					q.push(v);
				}
			}
		}
		q.pop();
		vis[u] = false;
	}
	return dis[t] != inf / 2;
}

void argument() {
	int u = t;
	int f = inf / 2;
	while (~pre[u]) {
		f = min(f, e[pre[u]].f);
		u = e[pre[u] ^ 1].v;
	}
	flow += f;
	cost += f * dis[t];
	u = t;
	while (~pre[u]) {
		e[pre[u]].f -= f;
		e[pre[u] ^ 1].f += f;
		u = e[pre[u] ^ 1].v;
	}
}

pair<int, int> work() {
	flow = 0;
	cost = 0;
	while (spfa()) argument();
	return { flow, cost };
}

void init(int s_, int t_, int vtot_) {
	s = s_;
	t = t_;
	vtot = vtot_;
	etot = 0;
	for (int i = 1; i <= vtot; i++) head[i] = -1;
}

void solve() {

	cin >> n >> m;
	if (!m) {
		cout << n << '\n';
		return;
	}

	init(n + n + 1, n + n + 2, n + n + 2);
	for (int i = 1; i <= n; i++) p[i] = { 1, n };
	//建图
	for (int i = 1, pos, l, r, v; i <= m; i++) {
		cin >> pos >> l >> r >> v;
                //记录每个位置可填的数的范围
		if (pos == 1) {
			for (int j = l; j <= r; j++) {
				p[j].first = max(p[j].first, v);
			}
		}
		else {
			for (int j = l; j <= r; j++) {
				p[j].second = min(p[j].second, v);
			}
		}
	}

	for (int i = 1; i <= n; i++) {
                //源点和数字建边
		for(int j = 1; j <= n; j++)
			addedge(s, i, 1, j * 2 - 1);
                //位置和汇点建边
		addedge(i + n, t, 1, 0);
                //数字和位置建边
		for (int j = p[i].first; j <= p[i].second; j++) 
			addedge(j, i + n, 1, 0);
	}
 
	pair<int, int>ans = work();
	if (ans.first != n) {
		cout << -1 << '\n';
		return;
	}
	cout << ans.second << '\n';
}

signed main() {
	IOS;
	int _t = 1;
	// cin >> _t;
	while (_t--) {
		solve();
	}
}
posted @ 2022-10-02 21:47  Yaqu  阅读(23)  评论(0)    收藏  举报