正睿 2025 NOIP 20连测 Day7

T1

给一个字符串有 01?,求有多少种把 ? 填成 0/1 的方案使得字符串能划分成两段并使得前一段字典序不大于后一段。

诈骗题。不合法当且仅当字符串形如 100000...

T2

袋中共有 \(n\) 个记忆碎片,第 \(i\) 个碎片上刻着一个整数 \(a_i\)

每一次整理操作中,Madeline 可以选择若干个碎片并将它们同时从袋中拿出。但她的心智有些奇特,她只能接受以下两种整理方式之一:

  1. 她可以一次性拿出若干个写着相同整数的碎片。
  2. 或者,她可以一次性拿出若干个写着互不相同数字的碎片。

显然,如果她只拿出一个碎片,也符合规则。

她希望通过一系列操作,将袋中的碎片数减少到正好 \(k\) 个。

你需要帮助她计算,对于每个 \(k=0,1,2,\ldots,n-1\),Madeline 至少需要进行多少次操作,才能让袋中恰好剩下 \(k\) 个碎片。

\(n\le 10^6\)

考场上傻逼了,以为我的解法复杂度不对,于是用了一个臆想的优化。

\(a_i\)\(i\) 出现次数,将 \(a_i\) 排序。

考虑枚举我们用了 \(x\) 次操作 2 和 \(y\) 次操作 1。此时最多能拿走 \(\sum \min(a_i,x)+\sum_{i=1}^y \max(a_i-x,0)\) 个碎片,然后你发现,当确定了 \(x\) 之后,只有使得 \(a_y\ge x\)\(y\) 才有贡献,所以能优化一下,由于 \(\sum a_i\)\(n\),所以最终复杂度 \(O(n\log n)\)

void work() {
	int n;
	cin >> n;
	vector<int> cnt(n + 1);
	for (int i = 1; i <= n; ++i) {
		int x; cin >> x;
		cnt[x]++;
	}
	vector<int> c;
	c.push_back(0);
	for (int i = 1; i <= n; ++i) {
		if (cnt[i]) c.push_back(cnt[i]);
	}
	sort(c.begin(), c.end());
	int m = c.size() - 1;
	int c0 = min(m, c[m]);
	vector<int> sm(n + 1);
	for (int i = 1; i <= m; ++i)
		sm[i] = sm[i - 1] + c[i];
	vector<int> f(n + 1);
	for (int x = 0; x <= c0; ++x) {
		for (int y = 0; y <= c0 && c[min(m, m - y + 1)] >= x; ++y) {
			int i = x + y;
			auto calc = [&](int x) {
				int t = prev(upper_bound(c.begin(), c.end(), x)) - c.begin();
				int ret = sm[t] + x * (m - t); // sum min(ai, x)
				int k = max(t, m - (i - x) + 1);
				ret += (sm[m] - sm[k - 1]) - x * (m - k + 1); // sum max(ai-x, 0)
				return ret;
			};
			f[i] = max(calc(x), f[i]);
		}
	}
	for (int i = 1; i <= c0; ++i)
		f[i] = max(f[i], f[i - 1]);
	vector<int> ans(n + 1);
	int j = 0;
	for (int i = 1; i <= c0; ++i) {
		while (j < f[i]) {
			ans[++j] = i;
		}
	}
	reverse(ans.begin() + 1, ans.end());
	for (int i = 1; i <= n; ++i)
		cout << ans[i] << ' ';
	cout << endl;
}

T3

一天,她忽然心血来潮,想知道是否存在“三分图”的概念。于是,她定义了三分图:一张无向图被称为三分图,当且仅当存在至少一种将图中每个顶点染成红、黄、蓝三色之一的方案,使得任意一条边的两端点颜色不同。

与此同时,Badeline 正在学习排列相关的知识。灵机一动,他想到了可以通过排列来构造一张无向图:对于一个 \(1 \sim n\) 的排列 \(p\),构造一张由 \(n\) 个顶点构成的无向图 \(G(p)\),图中顶点 \(u\) 和顶点 \(v\)\(u < v\))之间有一条无向边,当且仅当 \(p_u > p_v\)

一天,Madeline 和 Badeline 交流时发现,他们各自的研究内容有一定关联。于是他们定义了一个排列 \(p\) 是一个好排列,当且仅当 \(G(p)\) 是一张三分图。

然而,他们并不知道如何判断一个排列是否是好排列。于是,他们向你求助——请你判断给定的排列 \(w\) 是否是好排列。

不仅如此,他们还希望你计算一个区间内有多少个好排列。具体来说,给定两个排列 \(p\)\(q\),请你计算有多少个排列 \(w\) 满足:

  1. \(w\) 的字典序 \(\ge p\)\(\le q\)
  2. \(w\) 是一个好排列。

由于答案可能很大,你只需输出答案对 \(998{,}244{,}353\) 取模的结果。

原题 三分图 - Problem - QOJ.ac

先注意到一个排序合法当且仅当他能被分为三个上升的子序列。

然后如果没有字典序的限制,是这个:

写的真的有点好。加上字典序的限制就考虑先差分。接下来考虑如何算字典序小于等于一个 \(p\) 的合法排列个数,可以类似于数位 DP 的样子,钦定前 \(1\sim i\) 位与 \(p\) 相同,枚举第 \(i+1\) 位填什么,然后根据你钦定的这些数,算出此时的 \((i,j,k)\) 即可。

复杂度 \(O(n^3 + Tn^2 \log n)\)

const int MAXN = 305, mod = 998244353, N = 300;
int n, p[MAXN], q[MAXN], f[MAXN][MAXN][MAXN];

int add(int x, int y) {
	x += y;
	if (x >= mod) x -= mod;
	return x;
}

void init() {
	vector<vector<vector<int>>> g, g1, g2;
	g.resize(N + 1);
	g1.resize(N + 1);
	g2.resize(N + 1);
	for (int i = 0; i <= N; ++i) {
		g[i].resize(N + 1);
		g1[i].resize(N + 1);
		g2[i].resize(N + 1);
		for (int j = 0; j <= N; ++j) {
			g[i][j].resize(N + 1);
			g1[i][j].resize(N + 1);
			g2[i][j].resize(N + 1);
		}
	}
	g[0][0][0] = 1;
	auto update = [&](int a) {
		for (int b = 0; b <= N; ++b) {
			for (int c = 0; c <= N; ++c) {
				g1[a][b][c] = add(g1[a][b][c], g[a][b][c]);
				if (b) g1[a][b][c] = add(g1[a][b][c], g1[a][b - 1][c]);
				g2[a][b][c] = add(g[a][b][c], g2[a][b][c]);
				if (c) g2[a][b][c] = add(g2[a][b][c - 1], g2[a][b][c]);
			}
		}
	};
	update(0);
	for (int a = 1; a <= N; ++a) {
		for (int b = 0; b <= a; ++b) {
			for (int c = 0; c <= b; ++c) {
				if (a > b) {
					g[a][b][c] = add(g[a][b][c], g[a - 1][b][c]);
				}
				if (b > c) {
					g[a][b][c] = add(g[a][b][c], g1[a - 1][b - 1][c]);
				}
				if (c > 0) {
					g[a][b][c] = add(g[a][b][c], g2[a - 1][b - 1][c - 1]);
				}
			}
		}
		update(a);
	}
	for (int a = 0; a <= N; ++a) {
		for (int b = 0; b <= a; ++b) {
			for (int c = 0; c <= b; ++c) {
				f[a - b][b - c][c] = g[a][b][c];
				// if (g[a][b][c]) cout << a << ' ' << b << ' ' << c << endl;
			}
		}
	}
	// cout << f[0][0][0] << ' ' << g[2][2][1] << endl;
}

struct _bit {
	int tr[MAXN];
	int lowbit(int x) { return x & (-x); }
	int query(int x) {
		if (!x) return 0;
		int ret = 0;
		while (x) {
			ret += tr[x];
			x -= lowbit(x);
		}
		return ret;
	}
	void modify(int x, int v) {
		while (x <= n) {
			tr[x] += v;
			x += lowbit(x);
		}
	}
} t;

int calc(int *p) {
	int i = 0;
	vector<int> used(n + 1, false);
	vector<int> seq(3);
	for (int i = 1; i <= n; ++i)
		t.modify(p[i], 1);
	int ans = 0, ok = 0;
	while (i < n) {
		auto update = [](vector<int> &v, int x) {
			if (x > v[2]) return v[2] = x, true;
			if (x > v[1]) return v[1] = x, true;
			if (x > v[0]) return v[0] = x, true;
			return false;
		};
		for (int j = 1; j < p[i + 1]; ++j) {
			if (used[j]) continue;
			vector<int> now(seq.begin(), seq.end());
			if (!update(now, j)) continue;
			t.modify(j, -1);
			if (!t.query(now[0])) {
				int a = t.query(now[1]) - t.query(now[0]),
					b = t.query(now[2]) - t.query(now[1]),
					c = t.query(n) - t.query(now[2]);
				// cout << i << ' ' << j << ' ' << a << ' ' << b << ' ' << c << ' ' << f[a][b][c] << endl;
				// cout << now[0] << ' ' << now[1] << ' ' << now[2] << endl;
				ans = add(ans, f[a][b][c]);
			}
			t.modify(j, 1);
		}
		++i;
		used[p[i]] = true;
		t.modify(p[i], -1);
		if (!(ok = update(seq, p[i]))) break;
	}
	if (ok && !t.query(seq[0])) {
		int a = t.query(seq[1]) - t.query(seq[0]),
			b = t.query(seq[2]) - t.query(seq[1]),
			c = t.query(n) - t.query(seq[2]);
		// cout << "final " << a << ' ' << b << ' ' << c << ' ' << f[a][b][c] << endl;
		ans = add(ans, f[a][b][c]);
	}
	// cout << "failed at " << i << endl;
	memset(t.tr, 0, sizeof t.tr);
	return ans;
}

void work() {
	cin >> n;
	for (int i = 1; i <= n; ++i)
		cin >> p[i];
	for (int i = 1; i <= n; ++i)
		cin >> q[i];
	bool res = prev_permutation(p + 1, p + 1 + n);
	if (!res) cout << calc(q) << endl;
	else cout << (calc(q) - calc(p) + mod) % mod << endl;
}

T4

Madeline 喜欢登山。一天,她与 Badeline 一起来到了两座山脉——Celeste 和 Dolomites——之间。这两座山脉各由 \(n\) 座山峰组成,Celeste 山脉从左到右的山峰高度分别为 \(a_1, a_2, \ldots, a_n\),而 Dolomites 山脉从左到右的山峰高度分别为 \(b_1, b_2, \ldots, b_n\)

现在,她们将选择一个连续的区间 \([l, r]\),并分别进行攀登:Madeline 攀登 Celeste 山脉,Badeline 攀登 Dolomites 山脉。然而,她们都不喜欢攀登过高的山峰,因此会各自选择所在区间中高度最小的一座山峰。也就是说,Madeline 将攀登高度为 \(\min_{i=l}^r a_i\) 的山峰,而 Badeline 将攀登高度为 \(\min_{i=l}^r b_i\) 的山峰。

Madeline 希望两人所攀登的山峰高度尽可能接近,这样她们就能在山顶上更清楚地看到彼此。

接下来她们一共有 \(q\) 个旅行计划,每个旅行计划可以用三个整数 \((L, R, k)\) 来表示,她们希望在 \([L, R]\) 内选择一个长度为 \(k\) 的子区间,在这个子区间上分别进行攀登时,两人所攀登山峰高度差距最小。

形式化的,对于每个计划 \((L, R, k)\),你需要找到一个 \(l\) 满足 \([l, l+k-1] \subseteq [L, R]\),使得
\(\left| \min_{i=l}^{l+k-1} a_i \;-\; \min_{i=l}^{l+k-1} b_i \right|\)
的差的绝对值最小,并告诉她们这个最小的差。

待补。

posted @ 2025-10-22 22:15  小蛐蛐awa  阅读(17)  评论(0)    收藏  举报