[CF1408H] Rainbow Triples
[题目链接]
http://codeforces.com/contest/1408/problem/H
[题解]
首先答案有个上界 \(\lfloor \frac{cntz}{2} \rfloor\) , 其中 \(cntz\) 为序列中 \(0\) 的个数。
将序列一分为二。 左边集合 \(L\) 满足其右边都能找到至少 \(\lfloor \frac{cntz}{2} \rfloor\) 个 \(0\)。 而右集合 \(R\) 满足其左边都能找到至少 \(\lfloor \frac{cntz}{2} \rfloor\) 个 \(0\)。
这样 , 左集合里的数只关心左边取到的 \(0\) , 而右集合里的数只关心右边取到的 \(0\)。
考虑网络流。
将源点 \(S\) 向每个权值连一条容量为 \(1\) 的边。
将该权值向左侧最靠右的和右侧最靠左的两个这样权值的点连一条容量为 \(1\) 的边 (贪心地使能匹配的 \(0\) 个数尽可能多)
将 \(L\) 集合中每个位置 \(L_{x}\) 向 \(L_{x - 1}\) 连一条容量为 \(+\infty\) 的边。
将 \(R\) 集合中每个位置 \(R_{x}\) 向 \(R_{x + 1}\) 连一条容量为 \(+\infty\) 的边。
将 \(a_{i} = 0\) 的点向汇点 \(T\) 连容量为 \(1\) 的边。
答案即为 \(S\) 到 \(T\) 的最大流与 \(\lfloor \frac{cntz}{2} \rfloor\) 取最小值。
直接暴力做不可行。 因为有最大流 = 最小割。 所以转化为最小割来计算。
不妨枚举一个前缀的 \(0\) 将其割去。 用线段树维护割掉每个后缀的代价。 维护区间最小值即可。
时间复杂度 : \(O(NlogN)\)
[代码]
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i , l , r) for (int i = (l); i < (r); ++i)
const int MN = 5e5 + 5;
int N , A[MN] , mn[MN << 2] , tg[MN << 2] , L[MN] , R[MN] , cnt , sum[MN] , type[MN];
inline void chkmin(int &x , int y) {
x = min(x , y);
}
inline void pushup(int now) {
mn[now] = min(mn[now << 1] , mn[now << 1 | 1]);
}
inline void build(int now , int l , int r) {
tg[now] = 0;
if (l == r) {
mn[now] = cnt + l;
return;
}
int mid = l + r >> 1;
build(now << 1 , l , mid); build(now << 1 | 1 , mid + 1 , r);
pushup(now);
}
inline void pushdown(int now) {
if (tg[now] != 0) {
mn[now << 1] += tg[now]; tg[now << 1] += tg[now];
mn[now << 1 | 1] += tg[now]; tg[now << 1 | 1] += tg[now];
tg[now] = 0;
}
}
inline void dec(int now , int l , int r , int ql , int qr) {
if (l == ql && r == qr) {
--mn[now];
--tg[now];
return;
}
int mid = l + r >> 1; pushdown(now);
if (mid >= qr) dec(now << 1 , l , mid , ql , qr);
else if (mid + 1 <= ql) dec(now << 1 | 1 , mid + 1 , r , ql , qr);
else dec(now << 1 , l , mid , ql , mid) , dec(now << 1 | 1 , mid + 1 , r , mid + 1 , qr);
pushup(now);
}
int main() {
int T; scanf("%d" , &T);
while (T--) {
scanf("%d" , &N); int ans = 0 , m , M; cnt = 0;
for (int i = 1; i <= N; ++i) scanf("%d" , &A[i]);
for (int i = 1; i <= N; ++i) L[i] = R[i] = 0;
for (int i = 1; i <= N; ++i) sum[i] = sum[i - 1] + (A[i] == 0);
m = sum[N] >> 1;
for (int i = 1; i <= N; ++i) type[i] = (sum[i] > m) + 1;
for (int i = 1; i <= N; ++i) if (type[i] == 1 && A[i]) L[A[i]] = i;
for (int i = N; i >= 1; --i) if (type[i] == 2 && A[i]) R[A[i]] = i;
for (int i = 1; i <= N; ++i) cnt += (L[i] || R[i]);
build(1 , 0 , M = sum[N] - m);
for (int i = 1; i <= N; ++i) if (!L[i] && R[i]) dec(1 , 0 , M , sum[N] - sum[R[i] - 1] , M);
ans = min(m , mn[1]);
for (int i = 1; i <= N && type[i] == 1; ++i)
if (L[A[i]] == i) {
if (R[A[i]]) dec(1 , 0 , M , sum[N] - sum[R[A[i]] - 1] , M);
else dec(1 , 0 , M , 0 , M);
chkmin(ans , sum[i] + mn[1]);
}
printf("%d\n" , ans);
}
return 0;
}