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();
}
posted @ 2022-11-23 15:00  DCH233  阅读(36)  评论(0编辑  收藏  举报