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();
}
}