CF1408H Rainbow Triples
CF1408H Rainbow Triples
太喜欢这道题啦~~
通过观察问题,不难发现答案有几个上界,如\(\lfloor \frac{n}{3} \rfloor\), \(\lfloor \frac{ct_0}{2}\rfloor\)等等。
想象选三元组的过程是先选\(b\)然后再让两旁的0
与之匹配,那么有一个很棒的性质:我们可以将所有0
划分为两组,大小为\(\frac{ct_0}{2}\),那么在左边的\(b\)一定可以和右边的0
匹配,右边的\(b\)一定可以和左边的0
匹配。
把整个序列分成左右两部分,就只用考虑一边的匹配了,问题得到简化。
容易发现对于每种颜色,有用的只有左边的点中最靠右的和右边的点中最靠左的。
接下来可以考虑一个最大流模型,\(S\)向每种颜色连边,每种颜色向有用的两类点连边,然后点向左侧或右侧的0
连边,左边0
向更左的0
连边,右边类似,所有0
再向T
连边。最大流即答案。
然而这个数据规模提示我们需要优化这个网络流,考虑模拟网络流。最大流肯定没法做,转化成最小割。
首先颜色和有用的两类点之间的边,两类点和最近的0
之间的边一定不会被割掉,因为可以直接割掉这种\(S\)连向这种颜色的边使得答案不劣。
同时,考虑到我们在0
之间的边流量为\(+\infty\),所以只能割掉左边的一个前缀的0
或者是右边的一个后缀的0
。
那么做法就呼之欲出了,枚举左边割掉了几个0
,维护右边割掉每个后缀的0
还需要多割几条边保证图不在联通,那么只需要支持区间修改,和全局最小值,用一棵线段树来维护即可。
给你看代码(✪ω✪)
#include <cstdio>
#include <iostream>
#include <vector>
#define LL long long
using namespace std;
template <typename T>
inline void read(T &x) {
x = 0; int f = 0; char ch = getchar();
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
for(; isdigit(ch); ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);
if(f) x = ~x + 1;
}
const int N = 5e5 + 10;
struct SegTree {
#define lc (k << 1)
#define rc (k << 1 | 1)
#define mid ((l + r) >> 1)
int tr[N << 2], tag[N << 2];
inline void update(int k) {tr[k] = min(tr[lc], tr[rc]);}
inline void pushdown(int k) {
if(!tag[k]) return;
tr[lc] += tag[k], tr[rc] += tag[k];
tag[lc] += tag[k], tag[rc] += tag[k], tag[k] = 0;
}
void build(int k, int l, int r) {
if(l == r) return tr[k] = l - 1, tag[k] = 0, void();
tag[k] = 0, build(lc, l, mid), build(rc, mid + 1, r), update(k);
}
void modify(int k, int l, int r, int L, int R, int v) {
if(r < L || l > R) return;
if(L <= l && r <= R) return tr[k] += v, tag[k] += v, void();
pushdown(k), modify(lc, l, mid, L, R, v), modify(rc, mid + 1, r, L, R, v); update(k);
}
int query(){return tr[1];}
#undef lc
#undef rc
#undef mid
}s;
int n, p[N], pre[N], suf[N], vis[N], lp[N], rp[N], pos, tot;
void solve() {
read(n);
for(int i = 1; i <= n; ++i) read(p[i]);
pre[0] = suf[n + 1] = tot = 0;
for(int i = 1; i <= n; ++i) vis[i] = lp[i] = rp[i] = 0;
for(int i = 1; i <= n; ++i) pre[i] = pre[i - 1] + !p[i];
for(int i = n; i >= 1; --i) suf[i] = suf[i + 1] + !p[i];
for(int i = 1; i <= n; ++i) if(p[i] && !vis[p[i]]) {vis[p[i]] = 1, ++tot;}
for(int i = 1; i <= n; ++i) if(pre[i] == pre[n] / 2) {pos = i; break;}
for(int i = pos; i >= 1; --i)
if(p[i] && !lp[p[i]]) lp[p[i]] = i;
for(int i = pos + 1; i <= n; ++i)
if(p[i] && !rp[p[i]]) rp[p[i]] = suf[i] + 1;
int m = suf[pos + 1] + 1;
s.build(1, 1, m);
for(int i = 1; i <= n; ++i)
if(!lp[i] && rp[i]) s.modify(1, 1, m, rp[i], m, -1);
int ans = tot + s.query();
for(int i = 1, cur = 0; i <= pos; ++i) {
if(!p[i]) ++cur;
if(i == lp[p[i]]) {
if(rp[p[i]]) s.modify(1, 1, m, rp[p[i]], m, -1);
else s.modify(1, 1, m, 1, m, -1);
}
ans = min(ans, cur + tot + s.query());
}
printf("%d\n",min(ans, pre[n] / 2));
return ;
}
int main() {
int T; read(T);
while(T--) solve();
}