The 2024 ICPC Asia East Continent Online Contest (I)
題意
構造長度為 \(2n\) 的合法括號序列。
對於每個左括號在的位置 \(i\), 都有顏色 \(c_i\) 和價值 \(v_i\)。
左括號顏色視為所在位置顏色, 價值同理。
對於每個顏色,滿足左括號為該顏色的個數 \(\geq l_i\)。
求滿足以上條件下,最大左括號的價值和。
\(n \leq 100, m \leq n, v_i \leq 10^9\)。
做法
是費用流,關鍵在於建模。
- 保證合法括號序列
首先構建點 \(1 \dots 2n\) 表示所有括號序列。
把原點 \(S\) 連向所有奇數點,即 \(S \to 1, S\to 3, \dots, S \to 2n - 1\)。
流量,邊權 為 \((1, 0)\), 即表示左括號在這些點。
同時,對 \(i + 1 \to i\), 連 \((+\infty, 0)\), 表示左括號可以向左移動。
可以發現, 這樣構造出來的括號序列總是合法的, 總能保證左右括號匹配且任意字首左括號個數大於等於右括號個數。
- 顏色限制
首先, 每個位置 \(i\) 連向對應顏色 \(c_i\), 邊權為 \(-v_i\), 流量為 \(1\)。
為了保證顏色 \(i\) 有 \(l_i\) 個, 連 \(c_i \to T\) 時, 流量為 \(l_i\), 邊權為 \(-\infty\),(可以是極小的數 \(-10^{13}\)) 表示一定要選。
對於超過 \(l_i\) 的部分,連 \(c_i \to T\) 的 \((\infty, 0)\) 的邊, 表示可以選。
- 跑最小費用最大流即可
求得 \(\lfloor\frac{\text{mincost}}{-10^{13}}\rfloor = \sum l_i\), 即有解,
最後的答案為 \(\text{mincost} \mod (-10^{13})\)。
code
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 10100;
const ll INF = 1e13;
int n, m, s, t;
int col[N], val[N], l[N];
struct edge {
int v, len, next;
ll w;
} e[N];
int cnt;
int first[N], cur[N];
void add(int u, int v, int len, ll w) {
++ cnt;
e[cnt].v = v;
e[cnt].len = len;
e[cnt].w = w;
e[cnt].next = first[u];
first[u] = cnt;
}
void Add(int u, int v, int len, ll w) {
add(u, v, len, w);
add(v, u, 0, -w);
}
void init() {
cnt = 1;
s = 2 * n + m + 1, t = 2 * n + m + 2;
for (int i = 1; i <= t; i ++)
first[i] = 0;
}
bool vis[N];
ll dis[N];
bool bfs() {
memcpy(cur, first, sizeof(first));
for (int i = 1; i <= t; i ++)
vis[i] = 0, dis[i] = INF;
queue<int> q;
q.push(s);
dis[s] = 0;
while (!q.empty()) {
int u = q.front(); q.pop();
vis[u] = 0;
for (int i = first[u]; i; i = e[i].next) {
int v = e[i].v, len = e[i].len;
ll w = e[i].w;
if (!len || dis[v] <= dis[u] + w) continue;
dis[v] = dis[u] + w;
if (!vis[v]) {
q.push(v),
vis[v] = 1;
}
}
}
return dis[t] != INF;
}
ll cost;
int dfs(int u, int flow) {
if (u == t) {
return flow;
}
int ans = 0;
vis[u] = 1;
for (int &i = cur[u]; i; i = e[i].next) {
int v = e[i].v, len = e[i].len; ll w = e[i].w;
if (vis[v] || dis[v] != dis[u] + w || !len) continue;
int out = dfs(v, min(len, flow));
if (out) {
ans += out;
cost += 1ll * out * w;
e[i].len -= out;
e[i ^ 1].len += out;
flow -= out;
}
}
return ans;
}
ll dinic() {
ll ans = 0;
while (bfs()) {
cost = 0;
dfs(s, 0x7fffffff);
ans += cost;
}
return ans;
}
void solve() {
cin >> n >> m;
init();
int suml = 0;
for (int i = 1; i <= m; i ++)
cin >> l[i], suml += l[i];
for (int i = 1; i <= 2 * n; i ++)
cin >> col[i];
for (int i = 1; i <= 2 * n; i ++)
cin >> val[i];
for (int i = 1; i <= 2 * n; i += 2)
Add(s, i, 1, 0);
for (int i = 1; i <= 2 * n - 1; i ++)
Add(i + 1, i, 0x7fffffff, 0);
for (int i = 1; i <= 2 * n; i ++)
Add(i, 2 * n + col[i], 1, - val[i]);
for (int i = 1; i <= m; i ++) {
Add(2 * n + i, t, l[i], -INF);
Add(2 * n + i, t, 0x7fffffff, 0);
}
ll cost = dinic();
if (cost / (-INF) == suml) {
cout << (-cost) % INF << endl;
} else
cout << -1 << endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T;
cin >>T;
while (T --)
solve();
return 0;
}
最後 %%%klii , 感謝他的圖和教導。