The 2022 ICPC Asia-East Continent Final Contest (EC-Final 2022)【杂题】

比赛链接

没做完。

A. Coloring

\(n\) 个元素,第 \(i\) 个元素有价值 \(w_i\),颜色 \(c_i\)。给定 \(s\),初始时只有 \(c_s=1\),其余 \(c_i\) 均为 \(0\)

可以进行任意操作:选择一个 \(1 \le i \le n\),花费 \(p_i\) 的代价令 \(c_{a_i} \gets c_i\)

你的收益是最后 \(c_i = 1\)\(w_i\) 之和减去操作的花费,求最大收益。

\(1 \le s \le n \le 5 \times 10^3\)\(-10^9 \le w_i \le 10^9\)\(0 \le p_i \le 10^9\)\(1 \le a_i \le n\)\(a_i \neq i\)


很炫酷的题。

整个图的结构是基环树。不妨假设 \(s\) 在环上,考虑变换,一定是环上连续一段 \(0\) 一段 \(1\) 变换,然后随着环上 \(01\) 变换,每次染到环上某个点时都可以往子树内然一段,于是子树里也会变成 \(01\) 分层的情况。

子树里如果 \(01\) 分成了最多 \(k\) 次,那么环上至少 \(01\) 变换 \(k\) 次。首先可以把子树部分的代价 DP 出来,即设 \(f(u,i)\) 表示 \(u\) 子树内分出至多 \(i\) 层时的答案,利用前缀 \(\max\) 转移,可以做到 \(\mathcal{O}(n^2)\)

然后考虑环上,记 \(r_i\) 为环上点 \(i\) 的变换次数。通过手玩可以得到结论:从 \(s\) 开始按照边的顺序,\(r\) 单调不增且极差 \(\le 2\)。因此我们可以枚举变换次数,在环上 DP 即可,具体来说可以设 \(dp(i,j)(0 \le j \le 2)\) 表示环上第 \(i\) 的点的变换次数为 \(r_s - j\) 时的最大值,这部分时间复杂度 \(\mathcal{O}(n)\)。总时间复杂度 \(\mathcal{O}(n^2)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <LL, LL> pi;
#define fi first
#define se second
constexpr int N = 5e3 + 5;
constexpr LL inf = 1e18;
bool Mbe;
int n, s, a[N], vis[N], cir[N], len;
vector <int> e[N];
LL w[N], p[N];
LL f[N][N], g[N][N];
void chkmx(LL &x, LL y) {
	x = x > y ? x : y;
}
void dfs(int u) {
	for (auto v : e[u]) {
		if (vis[v]) continue;
		dfs(v);
		for (int i = 0; i <= n; i++) {
			f[u][i] += g[v][i];
		}
	}
	for (int i = 0; i <= n; i++) {
		f[u][i] += w[u] * (i & 1) - p[u] * i;
		g[u][i] = f[u][i];
		if (i) {
			chkmx(g[u][i], g[u][i - 1]);
		}
	}
}
void solve() {
	cin >> n >> s;
	for (int i = 1; i <= n; i++) 
		cin >> w[i];
	for (int i = 1; i <= n; i++) 
		cin >> p[i];
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		e[a[i]].emplace_back(i);
	}
	int u = a[s];
	while (!vis[u]) {
		vis[u] = 1;
		cir[++len] = u;
		u = a[u];
	}
	if (!vis[s]) {
		dfs(s);
		cout << max(f[s][1], f[s][2]) + p[s] << "\n";
	} else if (len == 2) {
		dfs(s);
		dfs(a[s]);
		cout << max(f[s][1], max(f[s][2], f[s][1] + f[a[s]][1])) + p[s] << "\n";
	} else {
		reverse(cir + 1, cir + len + 1);
		for (int i = 1; i <= len; i++) {
			dfs(cir[i]); 
		}
		assert(cir[1] == s);
		f[s][0] = -inf;
		LL ans = -inf;
		static LL dp[3];
		for (int i = 0; i <= n; i++) {
			dp[0] = dp[1] = dp[2] = 0;
			for (int j = 1; j <= len; j++) {
				for (int k = 0; k < 3; k++) {
					if (i + k <= n) dp[k] += f[cir[j]][i + k];
					else dp[k] = -inf;
				}
				chkmx(dp[1], dp[2]);
				chkmx(dp[0], dp[1]);
			}
			chkmx(ans, dp[0]);
		}
		cout << ans + p[s] << "\n";
	}
}
bool Med;
int main() {
//	fprintf(stderr, "%.9lf\n", 1.0 * (&Mbe - &Med) / 1048576.0);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t; t = 1;
	while (t--) solve(); 
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << " ms\n";
	return 0;
}

B. Binary String

给定一个 \(01\)\(a_0a_1\cdots a_{n-1}\),定义一次变换如下:对于所有 \(0 \le i < n\),若 \(a_i = 0\)\(a_{(i+1) \bmod n} = 1\),交换 \(a_i\)\(a_{(i+1) \bmod n}\)。求有多少个本质不同的 \(01\) 串满足能够从 \(a\) 变换若干次得到,答案对 \(998244353\) 取模。

\(1 \le n \le 10^7\)


很炫酷的题。

以下的 “段” 均指长度大于 \(1\)\(0\)\(1\) 的连续段。手玩一下能够发现一些规律:

  • 对于一个 \(0\) 段,如果其左边不和某个 \(1\) 段相邻,变换后会整体往左移动一位。
  • 对于一个 \(1\) 段,如果其右边不和某个 \(0\) 段相邻,变换后会整体往右移动一位。
  • 如果一个 \(0\) 段接在一个 \(1\) 段后面,那么相当于两个段相撞,变换后长度都会减 \(1\)

这个过程会一直执行直到不存在 \(0\)\(1\) 的段。每次相撞会使段的长度减 \(1\),并且 \(0\) 段和 \(1\) 段的运动方向是相反的,所以显然变换是有限次。之后就会变成一个串在环上不断绕圈,求出这个串之后就只需要用 KMP 求一下最小整周期即可。

考虑怎么求出最后这个串。不妨假设 \(1\) 段总长度不小于 \(0\) 段总长度。

注意到这个过程和括号匹配很像阿,考虑如果不是环而是序列的话,实际上我们可以直接模拟一遍求出每个 \(0\) 连续段会在什么时候消失,以及所有 \(0\) 连续段消失后的串。而如果是环,考虑当前没有剩下的 \(1\) 去和 \(0\) 匹配的情况,由于是个环所以这些 \(0\) 会跑到末尾的位置,看起来就很烦。

那考虑规避掉这种情况。根据 Raney 引理,我们总是能找到一个起点位置,使得每个前缀中 \(1\) 段长度和均不小于 \(0\) 段长度和。于是直接做就行了,时间复杂度 \(\mathcal{O}(n)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <LL, LL> pi;
#define fi first
#define se second
#define all(x) x.begin(), x.end()
#define debug cerr << "[" << __LINE__ << "] "
constexpr int N = 1e7 + 5, mod = 998244353;
constexpr LL inf = 1e18;
bool Mbe;
string s;
int n, kmp[N], c[N], nc[N];
void solve() {
	cin >> s;
	int c0 = count(all(s), '0'), c1 = count(all(s), '1');
	if (c0 < c1) {
		string str = "";
		for (auto i : s) str += i ^ 1;
		reverse(all(str));
		swap(s, str);
	}
	n = s.size();
	s = ' ' + s;
	for (int i = 1; i <= n; i++) {
		if (s[i] == '0') {
			string str = " ";
			for (int j = 1; j <= n; j++) str += s[(i + j - 2) % n + 1];
			swap(s, str);
			break;
		}
	}
	int tot = 0;
	for (int i = 1; i <= n; ) {
		if (s[i] == '0') {
			int j = i + 1;
			while (j <= n && s[j] == '1') j++;
			c[++tot] = j - i - 1;
			i = j;
		}
	}
	int sum = 0, mn = 0, pos = 0;
	for (int i = 1; i <= tot; i++) {
		sum += c[i] - 1;
		if (sum < mn) {
			mn = sum;
			pos = i; 
		}
	}
	for (int i = 1; i <= tot; i++) {
		nc[i] = c[i];
	}
	for (int i = 1; i <= tot; i++) {
		c[i] = nc[(i + pos - 1) % tot + 1];
	}
	int ans = 0;
	for (int i = 1; i <= tot; i++) {
		if (c[i] <= 1) continue;
		int j = i, v = 0, sum = 0;
		while (true) {
			v += c[j] - 1;
			sum++;
			if (v <= 0) break;
			j++;
		}
		ans = max(ans, sum - 1);
		for (int k = i; k <= j; k++) {
			c[k] = 1;
		}
	}
	string str = " ";
	for (int i = 1; i <= tot; i++) {
		str += '0';
		if (c[i]) str += '1';
	} 
	int j = 0;
	for (int i = 2; i <= n; i++) {
		while (j && str[i] != str[j + 1]) j = kmp[j];
		if (str[i] == str[j + 1]) j++;
		kmp[i] = j;
	}
	int t = n - kmp[n];
	if (n % t == 0) ans += t;
	else ans += n;
	cout << ans << "\n";
}
bool Med;
int main() {
//	fprintf(stderr, "%.9lf\n", 1.0 * (&Mbe - &Med) / 1048576.0);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t; cin >> t;
	while (t--) solve(); 
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << " ms\n";
	return 0;
}

C. Best Carry Player 2

给定 \(x,k\),求最小正整数 \(y\) 使得列竖式计算 \(x+y\) 时进位次数恰好为 \(k\),若无解输出 \(-1\)

\(1 \le T \le 10^5\)\(1 \le x < 10^{18}\)\(0 \le k \le 18\)


考虑从高位往低位数位 DP,设 \(f_{i,j,k}\) 表示做到第 \(i\) 位,⼀共进了 \(j\) 位,后面是否有向前面进位,每次考虑下一位是否进位,以及进位或不进位情况下填的最小数字即可。时间复杂度 \(\mathcal{O}(\log^2 n)\)

注意一些细节:由于 \(y\) 必须为正,\(k=0\) 时需要特判。以及答案可能超过 \(10^{18}\),可以去掉末尾所有 \(0\) 再做。

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <LL, LL> pi;
typedef tuple <int, int, int> ti3;
#define fi first
#define se second
constexpr int N = 1e6 + 5, M = 20;
constexpr LL inf = 1e18;
bool Mbe;
LL p[M];
void init() {
	p[0] = 1;
	for (int i = 1; i <= 18; i++) p[i] = p[i - 1] * 10;
}
LL x; int k;
LL f[M][M][2];
void chkmn(LL &x, LL y) {
	x = min(x, y);
}
void mian() {
	cin >> x >> k;
	if (k == 0) {
		for (int i = 0; i <= 17; i++) {
			int cur = x / p[i] % 10;
			if (cur < 9) {
				cout << p[i] << "\n";
				return;
			} 
		}
		cout << p[18] << "\n";
		return;
	}
	int cnt = 0;
	while (x % 10 == 0) cnt++, x /= 10;
	for (int i = 0; i <= 18; i++) for (int j = 0; j <= k; j++) f[i][j][0] = f[i][j][1] = inf;
	f[0][0][0] = 0;
	if (x % 10) f[0][1][1] = 10 - x % 10;
	for (int i = 1; i <= 18; i++) {
		int cur = x / p[i] % 10;
		for (int j = 0; j <= k; j++) {
			chkmn(f[i][j][0], f[i - 1][j][0]);
			if (cur < 9) chkmn(f[i][j][0], f[i - 1][j][1]);
			if (j) {
				if (cur) chkmn(f[i][j][1], f[i - 1][j - 1][0] + (10 - cur) * p[i]);
				chkmn(f[i][j][1], f[i - 1][j - 1][1] + (9 - cur) * p[i]);
			} 
		}
	}
	cout << f[18][k][0];
	for (int i = 1; i <= cnt; i++) cout << "0";
	cout << "\n"; 
}
bool Med;
int main() {
//	fprintf(stderr, "%.9lf\n", 1.0 * (&Mbe - &Med) / 1048576.0);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	init(); 
	int t; cin >> t;
	while (t--) mian(); 
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << " ms\n";
	return 0;
}

G. Rectangle

给定 \(n\) 个矩形,选三条 \([0,10^9]\) 内的直线或竖线使得每个矩形至少和一条线有交。

求方案数对 \(998244353\) 取模后的结果。

\(1 \le n \le 10^5\)


很炫酷的题。

根据直线的情况分类,有三条横线,一横两竖,以及对称情况。

对于三条横线,只和所有区间的 \(y\) 坐标相关,所以是一个一维问题。考虑枚举中间线的位置,上面和下面的线是上面未被覆盖和下面未被覆盖的区间的交。

对于一横两竖,考虑对横线做扫描线,变成了你要支持删除一个区间,插入一个区间,问用两个点覆盖剩下区间的方案数。假设所有区间为 \([l_i, r_i]\)。首先左边的点 \(L ≤ \min r_i\), 右边的点 \(R ≥ \max l_i\)

维护 \(f(L)\) 表示左边的点在 \(L\) 位置,右边的点 \(R\) 的上界。每次插入一条线段 \([l, r]\),会有如果 \(L < l\),那么 \(R\) 一定要 \(≤r\)。也就是对于一个前缀,有一个 \(≤ r\) 的限制。于是每个位置的限制是后缀最小值。可以用楼房重建线段树维护,也可以线段树分治把删除扔掉,这样就可以直接线段树二分加区间赋值维护。

细节很多,总时间复杂度 \(\mathcal{O}(n \log^2 n)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <LL, LL> pi;
#define fi first
#define se second
constexpr int N = 2e5 + 5, T = 1e9, mod = 998244353, inv6 = 166374059;
constexpr LL inf = 1e18;
bool Mbe;
#define x1 elma1
#define y1 elma2
#define x2 elma3
#define y2 elma4
#define m ((l + r) >> 1)
#define lc x << 1
#define rc x << 1 | 1 
void chkmx(int &x, int y) { x = x > y ? x : y; }
void chkmn(int &x, int y) { x = x < y ? x : y; }
void add(int &x, LL y) {
	x = x + y >= mod ? x + y - mod : x + y;
}
int n, x1[N], y1[N], x2[N], y2[N], yy[N], ans, lp[N], rp[N];
int cx, cy, tx[N], ty[N];
int len[N << 2], mx[N << 2], tag[N << 2], sum[N << 2], lmax[N], rmin[N];
void build(int x, int l, int r) {
	sum[x] = mx[x] = tag[x] = 0, len[x] = ty[r + 1] - ty[l];
	if (l == r) return;
	build(lc, l, m), build(rc, m + 1, r);
}
vector <pi> g1;
vector <pi> g2;
vector <pi> g3;
void push_tag(int x, int v) {
	g1.emplace_back(x, mx[x]);
	g2.emplace_back(x, tag[x]);
	g3.emplace_back(x, sum[x]);
	mx[x] = tag[x] = v;
	sum[x] = 1LL * len[x] * v % mod;
}
void push_down(int x) {
	if (tag[x]) {
		g2.emplace_back(x, tag[x]);
		push_tag(lc, tag[x]);
		push_tag(rc, tag[x]);
		tag[x] = 0;
	}
} 
void push_up(int x) {
	g1.emplace_back(x, mx[x]);
	g3.emplace_back(x, sum[x]);
	mx[x] = mx[rc];
	sum[x] = (sum[lc] + sum[rc]) % mod;
}
void cov(int x, int l, int r, int ql, int qr, int v) {
	if (ql <= l && qr >= r) return push_tag(x, v);	
	push_down(x);
	if (ql <= m) cov(lc, l, m, ql, qr, v);
	if (qr > m) cov(rc, m + 1, r, ql, qr, v);
	push_up(x);
}
int qry(int x, int l, int r, int ql, int qr) {
	if (ql <= l && qr >= r) return sum[x];
	push_down(x);
	int res = 0;
	if (ql <= m) add(res, qry(lc, l, m, ql, qr));
	if (qr > m) add(res, qry(rc, m + 1, r, ql, qr));
	return res;
}
int findpos(int x, int l, int r, int v) {
	if (l == r) return l;
	push_down(x);
	if (mx[lc] > v) return findpos(lc, l, m, v);
	return findpos(rc, m + 1, r, v); 
}
vector <int> buc[N << 2];
void upd(int x, int l, int r, int ql, int qr, int id) {
	if (ql <= l && qr >= r) return buc[x].emplace_back(id), void();
	if (ql <= m) upd(lc, l, m, ql, qr, id);
	if (qr > m) upd(rc, m + 1, r, ql, qr, id);
}
void solve(int x, int l, int r) {
	int t1 = g1.size(), t2 = g2.size(), t3 = g3.size();
	for (auto id : buc[x]) {
		int nr = mx[1] <= y1[id] ? cy : findpos(1, 1, cy, y1[id]) - 1;
		if (yy[id] <= nr) cov(1, 1, cy, yy[id], nr, y1[id]);
	}
	if (l == r) {
		if (lmax[l]) {
			int L = lower_bound(ty + 1, ty + cy + 1, lmax[l]) - ty;
			if (L <= cy) {
				int R = mx[1] <= rmin[l] ? cy : findpos(1, 1, cy, rmin[l]) - 1;
				if (L <= R) {
					int val = qry(1, 1, cy, L, R);
					add(ans, 1LL * (1LL * (ty[R + 1] - ty[L]) * (rmin[l] + 1) % mod - val + mod) % mod * (tx[l + 1] - tx[l]) % mod);
				}
			}
		}
	} else solve(lc, l, m), solve(rc, m + 1, r);
	while (g1.size() > t1) {
		auto [x, y] = g1.back();
		mx[x] = y;
		g1.pop_back(); 
	}
	while (g2.size() > t2) {
		auto [x, y] = g2.back();
		tag[x] = y;
		g2.pop_back();
	}
	while (g3.size() > t3) {
		auto [x, y] = g3.back();
		sum[x] = y;
		g3.pop_back();
	}
}
struct P1 {
	priority_queue <int> q1, q2;
	void insert(int x) {
		q1.push(x);
	}
	void erase(int x) {
		q2.push(x);
	}
	unsigned size() {
		return q1.size() - q2.size();
	}
	bool empty() {
		return q1.size() == q2.size();
	}
	int top() {
		while (q2.size() && q2.top() == q1.top()) q1.pop(), q2.pop();
		return q1.top();
	}
	void pop() {
		while (q2.size() && q2.top() == q1.top()) q1.pop(), q2.pop();
		q1.pop();
	}
};
struct P2 {
	priority_queue <int, vector <int>, greater <int>> q1, q2;
	void insert(int x) {
		q1.push(x);
	}
	void erase(int x) {
		q2.push(x);
	}
	unsigned size() {
		return q1.size() - q2.size();
	}
	bool empty() {
		return q1.size() == q2.size();
	}
	int top() {
		while (q2.size() && q2.top() == q1.top()) q1.pop(), q2.pop();
		return q1.top();
	}
	void pop() {
		while (q2.size() && q2.top() == q1.top()) q1.pop(), q2.pop();
		q1.pop();
	}
};
vector <int> ad[N];
int t1[N];
bool t2[N];
void solve() {
	for (int i = 1; i <= 2 * n + 5; i++) {
		ad[i].clear();
	}
	for (int i = 1; i <= 8 * n + 10; i++) {
		buc[i].clear();
	}
	cx = cy = 0;
	tx[++cx] = 1;
	ty[++cy] = 1;
	for (int i = 1; i <= n; i++) {
		tx[++cx] = x1[i];
		if (x2[i] != T) tx[++cx] = x2[i] + 1;
		ty[++cy] = y1[i];
		if (y2[i] != T) ty[++cy] = y2[i] + 1;
	}
	for (int i = 1; i <= cx; i++) lp[i] = 0, rp[i] = T + 1;
	sort(tx + 1, tx + cx + 1);
	sort(ty + 1, ty + cy + 1);
	cx = unique(tx + 1, tx + cx + 1) - tx - 1;
	cy = unique(ty + 1, ty + cy + 1) - ty - 1;
	tx[cx + 1] = T + 1;
	ty[cy + 1] = T + 1;
	build(1, 1, cy);
	for (int i = 1; i <= n; i++) {
		int x1 = lower_bound(tx + 1, tx + cx + 1, ::x1[i]) - tx;
		int x2 = lower_bound(tx + 1, tx + cx + 1, ::x2[i] + 1) - tx;
		chkmx(lp[x2], ::x1[i]);
		chkmn(rp[x1 - 1], ::x2[i]);
		yy[i] = lower_bound(ty + 1, ty + cy + 1, y2[i] + 1) - ty;
		if (x1 > 1) upd(1, 1, cx, 1, x1 - 1, i), ad[1].emplace_back(i), ad[x1].emplace_back(-i);
		if (x2 <= cx) upd(1, 1, cx, x2, cx, i), ad[x2].emplace_back(i);
	} 
	P1 nl;
	P2 nr;
	int L = 1, R = T;
	for (int i = 1; i <= cx; i++) {
		for (auto j : ad[i]) {
			if (j > 0) {
				nl.insert(y1[j]);
				nr.insert(y2[j]);
			} else {
				j *= -1;
				nl.erase(y1[j]);
				nr.erase(y2[j]);
			}
		}
		if (nl.empty()) lmax[i] = 0, add(ans, 1LL * T * (T - 1) / 2 % mod * (tx[i + 1] - tx[i]) % mod);
		else {
			lmax[i] = nl.top(), rmin[i] = nr.top();
			if (lmax[i] <= rmin[i]) {
				LL len = rmin[i] - lmax[i] + 1;
				add(ans, 1LL * (1LL * T * (T - 1) / 2 - 1LL * (T - len) * (T - len - 1) / 2) % mod * (tx[i + 1] - tx[i]) % mod); 
				swap(lmax[i], rmin[i]);
				lmax[i]++;
				rmin[i]--;
			}
		}
		if (lp[i]) {
			chkmx(L, lp[i]);
			chkmn(R, tx[i] - 1);
		}
		t1[i] = max(0, min(R, tx[i] - 1) - L + 1);
		t2[i] = R >= tx[i];
	}
	L = 1, R = T;
	for (int i = cx; i >= 1; i--) {
		if (rp[i] <= T) {
			chkmx(L, tx[i + 1]);
			chkmn(R, rp[i]);
		}
		int k1 = max(0, R - max(L, tx[i + 1]) + 1);
		bool k2 = L <= tx[i];
		int len = tx[i + 1] - tx[i];
		add(ans, 1LL * len * t1[i] % mod * k1 % mod);
		if (t2[i]) add(ans, 1LL * len * (len - 1) / 2 % mod * k1 % mod);
		if (k2) add(ans, 1LL * len * (len - 1) / 2 % mod * t1[i] % mod);
		if (t2[i] && k2) add(ans, 1LL * len * (len - 1) % mod * (len - 2) % mod * inv6 % mod);
	} 
	solve(1, 1, cx);
}
void mian() {
	ans = 0;
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> x1[i] >> y1[i] >> x2[i] >> y2[i];
	solve();
	for (int i = 1; i <= n; i++) swap(x1[i], y1[i]), swap(x2[i], y2[i]);
	solve();
	cout << ans << "\n";
}
bool Med;
int main() {
//	fprintf(stderr, "%.9lf\n", 1.0 * (&Mbe - &Med) / 1048576.0);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t = 1; cin >> t;
	while (t--) mian(); 
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << " ms\n";
	return 0;
}

K. Magic

你有一个序列 \(a_0,a_1,a_2\dots a_{2n}\),初始全为 \(0\)

给定 \(n\) 个区间赋值操作,第 \(i\) 个操作 \((l_i,r_i)(1\le l_i,r_i\le 2n)\) 表示把区间 \([l_i,r_i)\) 全部赋值为 \(i\)保证所有 \(l_i,r_i\) 互不相同

你可以指定一个执行操作的顺序,最大化 \(\sum_{i=0}^{2n-1}[a_i\ne a_{i+1}]\),输出这个最大值。

\(1\le n\le 5\times 10^3\)空间限制 \(\text{16.0Mb}\)


很炫酷的题。

先做一点基本转化:令 \(b_i = [a_i \neq a_{i-1}]\),则操作 \([l_i,r_i]\) 即令 \(b_{l_i} = b_{r_i} = 1\)\(b_j = 0(l_i + 1 \le j \le r_i-1)\)

换⼀种方式考虑:要选择若干个位置,使得这些位置最后贡献给答案,要求合法且选择的位置数量最多。对于区间 \([l_i, r_i]\)\([l_j, r_j]\),若 \(l_i < l_j < r_i < r_j\),那么如果选择 \(l_j\),就意味着 \([l_i, r_i]\) 要在 \([l_j, r_j]\) 之前执行;同理如果选择 \(r_i\),就意味着 \([l_j, r_j]\) 要在 \([l_i, r_i]\) 之前执行。

所以 \(r_i\)\(l_j\) 不能同时选择,所以⼀定是⼀个建无向图后的独立集。下面证明这是充分的:

证明
只要选择独立集后不会成环,那么就能够说明合法性。
如果成环,假设环上选了一个区间 \([l, r]\) 的右端点 \(r\),导致它比某个区间 \([l’,r’](l < l’ < r < r’)\) 要晚执行。由于 \(l’\) 不能被选择,那么下一个环上的点一定是由于选择了 \(r’\) 导致的。那么以此类推,在这条链上的区间 \(r\) 一定单调递增,所以不可能成环。因此该限制充分。

于是问题转为二分图最大独立集问题。使用 bitset 优化匈牙利求出最大匹配即可,时间复杂度 \(\mathcal{O}(\frac{n^3}{\omega})\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair <LL, LL> pi;
#define fi first
#define se second
constexpr int N = 1e4 + 5, M = 20;
bool Mbe;
int n, m, ans;
int q[N], hd, tl;
int l[N], r[N], col[N], mat[N], prex[N], prey[N];
bitset <N> rest, nxt, e[N];
bool bfs(int s, int n) {
	int pos = 0;
	q[hd = tl = 1] = s;
	rest.set();
	while (hd <= tl && !pos) {
		int u = q[hd++];
		nxt = rest;
		nxt &= e[u];
		for (int i = nxt._Find_first(); i <= n; i = nxt._Find_next(i)) {
			if (!mat[i]) {
				mat[i] = u;
				pos = u;
				break;
			}
			rest[i] = false;
			prex[mat[i]] = u;
			prey[mat[i]] = i;
			q[++tl] = mat[i];
		}
	}
	if (!pos) return false;
	for (int i = pos; i != s; i = prex[i]) {
		mat[prey[i]] = prex[i];
	}
	return true;
}
void solve() {
	cin >> n;
	m = ans = n * 2;
	for (int i = 1; i <= n; i++) {
		cin >> l[i] >> r[i];
		col[l[i]] = 0;
		col[r[i]] = 1;
	}
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++) {
			if (l[i] < l[j] && l[j] < r[i] && r[i] < r[j]) 
				e[l[j]][r[i]] = 1;
		}
	for (int i = 1; i <= m; i++)
		if (col[i] == 0 && bfs(i, m)) ans--;
	cout << ans << "\n";
}
bool Med;
int main() {
//	fprintf(stderr, "%.9lf\n", 1.0 * (&Mbe - &Med) / 1048576.0);
//	freopen("ex_data3.in", "r", stdin);
//	freopen("ex_data3.ans", "w", stdout);
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t = 1; 
	while (t--) solve(); 
//	cerr << 1e3 * clock() / CLOCKS_PER_SEC << " ms\n";
	return 0;
}
posted @ 2023-12-22 16:10  came11ia  阅读(110)  评论(0编辑  收藏  举报