Codeforces 1615G. Maximum Adjacent Pairs (3300)
给定长度为 的序列 , 中有些位置的值已经确定了,还有些位置的值没有确定,这些位置上的数是 ,需要替换成 的值。
填完所有为 的位置使得满足下列条件的 的数量最多,构造任意一种方案。
- ,。
。
有生之年在赛场上想出的题但没写完。
考虑到有值的部分会将整个序列划分成若干段,只有与有值的位置相邻的不确定的值的位置才有可能与其匹配。
考虑一段长度为 且全为 的区间 ,分 奇偶讨论。
- 若 为奇数,此时必定可以对 个两两匹配,那么剩下一个要么跟 匹配,要么和 匹配。根据贪心策略,这样必定最优,假设 和 都匹配,那么最多达到的贡献为 ,而前一种最多达到的贡献为 。最多贡献一样,但后一种匹配两个可能会导致与其它的不能匹配,而且若后一种能匹配,必定前一种也能匹配,所以前一种的贡献始终 后一种的贡献。
- 若 为偶数,此时只有两种情况。一种是 和 都匹配,中间 个两两匹配。另一种是不管两边的,直接把中间 个两两匹配。根据贪心策略,这两种能涵盖所有的情况。前一种方案虽然最多贡献大,但是可能会对其它的匹配造成影响,所以需要两者都考虑。
然后可以自然地想到使用 一般图最大匹配 的带花树算法来解决。
考虑连边。
-
若 为奇数,将 连向一个新建的点 , 也连向一个新建的点 。那么 只能和其中之一匹配。
-
若 为偶数,将 连向一个新建的点 , 连向一个新建的点 ,再将 和 。这恰好对应了讨论的两种情况,若 和 连, 和 连,那么就是两边都匹配;否则 与 连,则是两边都不匹配。
时间复杂度 , 是 的值域。
#include <bits/stdc++.h>
#define eb emplace_back
using namespace std;
const int N = 6e5 + 5, M = 12e5 + 5;
int n, a[N], id = 1, have[N], head[N], tot, ver[M], nxt[M];
int fa[N], vis[N], pre[N], match[N], dfn[N], timer; bool del[M], vs[N];
queue <int> q; vector <int> clr; vector < pair<int, int> > vec;
inline void add(int u, int v) {
ver[++tot] = v; nxt[tot] = head[u]; head[u] = tot;
ver[++tot] = u; nxt[tot] = head[v]; head[v] = tot;
}
inline int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
inline int lca(int u, int v) {
timer ++, u = find(u), v = find(v);
while(dfn[u] != timer) {
dfn[u] = timer;
u = find(pre[match[u]]);
if(v) u ^= v ^= u ^= v;
}
return u;
}
inline void blossom(int x, int y, int z) {
while(find(x) != z) {
pre[x] = y; y = match[x];
if(vis[y] == 2) vis[y] = 1, q.push(y), clr.eb(y);
if(x == find(x)) fa[x] = z;
if(y == find(y)) fa[y] = z;
x = pre[y];
}
}
inline bool bfs(int s) {
for (auto x : clr) vis[x] = pre[x] = 0, fa[x] = x; clr.clear();
while(!q.empty()) q.pop();
q.push(s); vis[s] = 1; clr.eb(s);
while(!q.empty()) {
int u = q.front(); q.pop();
for(int i = head[u]; i; i = nxt[i]) {
int v = ver[i];
if(vis[v] == 2 || find(u) == find(v)) continue;
if(vis[v] == 0) {
vis[v] = 2; pre[v] = u; clr.eb(v);
if(!match[v]) {
for(int x = v, lst; x; x = lst) {
lst = match[pre[x]];
match[x] = pre[x];
match[pre[x]] = x;
}
return 1;
}
vis[match[v]] = 1;
q.push(match[v]);
clr.eb(match[v]);
}
else {
int w = lca(u, v);
blossom(u, v, w); blossom(v, u, w);
clr.eb(v), clr.eb(w);
}
}
}
return 0;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n + n; ++ i) fa[i] = i;
for (int i = 1; i <= n; ++ i) scanf("%d", a + i), have[a[i]] = 1;
for (int i = 1; i < n; ++ i) if (a[i] == a[i - 1] && a[i]) vs[a[i]] = 1;
for (int i = 1, j; i <= n; i = j + 1) {
j = i; if (a[i]) continue;
while (!a[j] && j <= n) ++ j;
vec.emplace_back(i, j);
if ((j - i) & 1) {
if (i > 1 && !vs[a[i - 1]]) add(a[i - 1], n + i);
if (j <= n && !vs[a[j]]) add(a[j], n + i);
} else {
if (i > 1 && !vs[a[i - 1]]) add(a[i - 1], n + i);
if (j <= n && !vs[a[j]]) add(a[j], n + j - 1);
add(n + i, n + j - 1);
}
}
for (int i = 1; i <= n + n; ++ i) if (!match[i]) bfs(i);
for (auto p : vec) {
int l = p.first, r = p.second;
if ((r - l) & 1) {
if (l > 1 && match[n + l] == a[l - 1]) {
a[l] = a[l - 1];
for (int i = l + 1; i + 1 < r; i += 2) {
while (have[id]) ++ id;
a[i] = a[i + 1] = id ++;
}
} else {
for (int i = l; i + 1 < r; i += 2) {
while (have[id]) ++ id;
a[i] = a[i + 1] = id ++;
}
a[r - 1] = a[r];
}
} else {
if (match[n + l] == n + r - 1) {
for (int i = l; i + 1 < r; i += 2) {
while (have[id]) ++ id;
a[i] = a[i + 1] = id ++;
}
} else {
a[l] = a[l - 1], a[r - 1] = a[r];
for (int i = l + 1; i + 1 < r; i += 2) {
while (have[id]) ++ id;
a[i] = a[i + 1] = id ++;
}
}
}
}
for (int i = 1; i <= n; ++ i) printf("%d ", a[i] ? a[i] : 1);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】