XiaoQuQu 的 2025 CSP-S 第二轮模拟 ROUND2

C. 黑白游戏

https://www.luogu.com.cn/problem/P13544

首先观察题面,发现在第一行和第三行中的路径一定分别是一段前缀和一段后缀,而只有第二行是特殊的。

以下记 \(pre_i\)\(a_{1, i}\) 的前缀和,\(suf_i\)\(a_{3, i}\) 的后缀和,\(s_I\)\(a_{2, i}\) 的前缀和。

由于“每次操作会产生一定代价”这样的操作不好刻画,我们先将所有 \(c_i \gets -c_i\),这样每次操作的代价也变成了要加上去的权值。我们事实上是要找出一条从 \((1, 1)\)\((3, n)\) 的最长路。

容易想到一个状态 \(f_i\) 表示走到 \((2, i)\) 的最大权值。发现有以下转移:

\[\begin{aligned} f_i \gets &\max_{k \text{ S.T. } L_k \le i \le R_k} \left\{c_k + \max_{L_k \le j \le i}\left(pre_j + s_i - s_{j-1}\right) \right\}, \\ f_i \gets &\max_{k \text{ S.T. } L_k \le i \le R_k} \left\{c_k + \max_{L_k - 1 \le j < i}\left(f_{j} + s_{i} - s_{j}\right) \right\}. \end{aligned} \]

两个式子分别对应从上一行走过来,以及从这一行前面走过来的情况。

\(s_i\)\(j\) 无关,可以直接拆出来。

我们发现现在是要最大化一个形如 \(val_j+c_k\) 的东西,要求 \(j, i\) 同时被区间 \([L_k, R_k]\) 包含。

“被区间包含”这个条件可以用数据结构大力维护,但是有点史。我们有一个绝妙的 CDQ 分治做法。

我们在分治时动态维护所有完全在 \([l, r]\) 内的区间 \([L_k, R_k]\).

对于完全在 \([l, mid]\) 内的区间,直接递归下传到左边即可;完全在 \([mid + 1, r]\) 的区间同理。

对于跨过中点的区间,我们发现它既会在左右两边各自的转移时产生贡献,也会在 \([l, mid] \to [mid+1,r]\) 的转移时产生贡献。于是考虑将跨中点的区间 \([L_k, R_k]\) 拆成 \([L_k, mid]\)\([mid + 1, R_k]\) 两部分,分别下传到两侧。

在递归时,一个区间要么下传到一边,要么拆成两个形如 \([l, mid]\)\([mid + 1,r]\) 的前、后缀区间分别下传。容易发现对 \(L, R\) 相同的区间,我们只需保留 \(c_k\) 最大的即可,因为更小的那个不会产生任何作用,于是我们可以去重。去重后,在处理每个区间 \([l, r]\) 时至多保留了 \(2(r-l+1)-1\) 个这样的前、后缀区间,所以整个分治的过程中记录的区间总数总共不会超过 \(O((n+q) \log n)\) 个。这样一来时间复杂度就得到了保证。

接下来考虑处理跨过中点的转移。每个跨中点的区间 \([L_k, R_k]\) 中可以作为转移点的只有一段后缀 \([L_k, mid]\)。 此时 \(j\) 通过 \([L_k, R_k]\) 转移到 \(i\) 的限制就变成了 \(R_k \ge i, j \in [L_k, mid]\). 于是维护一个 \(cur\),按右端点从大到小扫这些区间,每遇到一个区间 \([L, R, c]\) 就用 \(\displaystyle \max_{k = L}^{mid} val_k + c\) 更新 \(cur\),然后再用 \(cur\) 更新 \(f_i\) 即可。

由于值域很小,去重和从右到左扫整个区间都可以用类似桶排的做法实现,总时间复杂度只有 CDQ 分治的 \(O((n+q) \log n)\)

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int N = 5e5 + 5, inf = 1e18;

int n, m, a[3][N], pre[N], suf[N], s[N], l, r, c, val[N], val2[N], f[N], mc[N];

struct opt
{
	int l, r, c;
};

vector<opt> vs, g[N];

vector<opt> uniq(vector<opt> &v)
{
	vector<opt> res; res.clear();
	for(int i = 0; i < v.size(); i++)
		g[v[i].l].push_back(v[i]);
	for(int i = 0; i < v.size(); i++)
	{
		if(g[v[i].l].empty()) continue;
		for(auto p : g[v[i].l]) mc[p.r] = max(mc[p.r], p.c);
		for(auto p : g[v[i].l])
		{
			if(mc[p.r] == -inf) continue;
			res.push_back({v[i].l, p.r, mc[p.r]}), mc[p.r] = -inf;
		}
		g[v[i].l].clear();
	}
	return res;
}

void solve(int l, int r, vector<opt> vm)
{
	vm = uniq(vm);
	if(vm.empty()) return;
	if(l == r)
	{
		f[r] = max(f[r], pre[r] + a[1][r] + vm[0].c);
		f[r] = max(f[r], f[r - 1] + a[1][r] + vm[0].c);
		return;
	}
	int mid = (l + r) >> 1;
	vector<opt> vl, vr;
	vl.clear(), vr.clear();
	for(auto pp : vm)
	{
		if(pp.l <= mid) vl.push_back({pp.l, min(mid, pp.r), pp.c});
		if(pp.r >= mid + 1) vr.push_back({max(mid + 1, pp.l), pp.r, pp.c}); 
		if(pp.l <= mid && pp.r >= mid + 1) g[pp.r].push_back(pp);
	}
	solve(l, mid, vl);
	for(int i = l - 1; i <= mid; i++)
		val[i] = f[i] - s[i], val2[i] = pre[i] - s[i - 1];
	for(int i = mid - 1; i >= l - 1; i--)
		val[i] = max(val[i], val[i + 1]), val2[i] = max(val2[i], val2[i + 1]);
	int cur = -inf, cur2 = -inf;
	for(int i = r; i >= mid + 1; i--)
	{
		for(auto p : g[i])
		{
			cur = max(cur, val[p.l - 1] + p.c);
			cur2 = max(cur2, val2[p.l] + p.c);
		}
		f[i] = max(f[i], max(cur + s[i], cur2 + s[i]));
	}
	for(int i = l - 1; i <= mid; i++) val[i] = val2[i] = -inf;
	for(auto pp : vm)
		if(pp.l <= mid && pp.r >= mid + 1) g[pp.r].clear();
	solve(mid + 1, r, vr);
	return;
}

signed main()
{
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n >> m;
	for(int i = 0; i <= n; i++)
		f[i] = val[i] = val2[i] = mc[i] = -inf;
	for(int i = 0; i <= 2; i++)
	for(int j = 1; j <= n; j++) cin >> a[i][j];
	for(int i = 1; i <= n; i++)
		pre[i] = pre[i - 1] + a[0][i];
	for(int i = n; i >= 1; i--)
		suf[i] = suf[i + 1] + a[2][i];
	for(int i = 1; i <= n; i++)
		s[i] = s[i - 1] + a[1][i];
	for(int i = 1; i <= m; i++)
	{
		cin >> l >> r >> c; c = -c;
		vs.push_back({l, r, c});
	}
	vs = uniq(vs);
	solve(1, n, vs);
	int ans = -2e18;
	for(int i = 1; i <= n; i++)
		ans = max(ans, f[i] + suf[i]);
	cout << ans;
	return 0;
}

D. 宇宙旅行

原题:https://www.luogu.com.cn/problem/AT_arc118_e

套路地改变枚举顺序,变为对每条路径,对所有合法排列计数并求和。

\(C\) 为任意一条从 \((0,0)\)\((n+1,n+1)\) 的路径经过的点集,\(P\) 为 所有合法障碍点集,\(Q\) 为题目已经确定的障碍点集。显然现在 S 和 P 都不确定,题目即求 \(\displaystyle \sum_C \sum_{P\supseteq Q} [C \cap P = \varnothing]\).

这个式子可以容斥,答案即为 \(\displaystyle \sum_C \sum_{P\supseteq Q} \sum_{P' \subseteq C \cap P} (-1)^{|P'|}\).

再改变枚举顺序,得到 \(\displaystyle \sum_C \sum_{P' \subseteq C} (-1)^{|P'|} \sum_{P\supseteq Q\cup P'} 1\).

\(P'\) 确定时,\(P\) 中确定的位置有 \(|Q\cup P'|\) 个,未确定的有 \(n - |Q\cup P'|\) 个,那么 \(P\) 的数量就是 \((n - |Q \cup P'|)!\),组合意义比较明显。

由于这个全排列的式子仅和 \(|Q \cup P'|\) 有关,我们可以放到最后计算。

我们尝试 DP. 判断一个新加入的点的是否合法,就是在判断横纵坐标是否与已有的点相同。由于路径中每一步只能向下或向右,我们只需要看当前行和当前列是否有点被钦定过。

不难想到设计状态 \(f_{i, j, k, 0/1, 0/1}\) 表示走到 \((i, j)\),当前选择的 \(|Q \cup P'| = k\),第 \(i\) 行是否被钦定过,第 \(j\) 行是否被钦定过。转移的时候将 \((-1)^{P'}\) 的容斥系数塞进式子里,每钦定一个点就乘上 \(-1\).

初始状态为 \(f_{0, 0, |Q|, 0, 0} = 1\).

#include <bits/stdc++.h>
using namespace std;

#define int long long
const int N = 205, mod = 998244353;

int n, a[N], f[N][N][N][2][2], fac[N], ans, s;
bool col[N], row[N];

signed main()
{
	ios :: sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n;
	for(int i = 1; i <= n; i++)
	{
		cin >> a[i];
		if(~a[i]) col[i] = row[a[i]] = 1, s++;
	}
	fac[0] = 1;
	for(int i = 1; i <= n + 1; i++) fac[i] = fac[i - 1] * i % mod;
	f[0][0][s][0][0] = 1;
	// for(int j = 1; j <= n + 1; j++)
		// f[0][j][s][0][0] = f[j][0][s][0][0] = 1;
	for(int k = s; k <= n; k++)
	for(int i = 0; i <= n + 1; i++)
	for(int j = 0; j <= n + 1; j++)
	for(int p = 0; p <= 1; p++)
	{
		for(int q = 0; q <= 1; q++)
		{
			if(i <= n)
			{
				f[i + 1][j][k][0][q] += f[i][j][k][p][q], f[i + 1][j][k][0][q] %= mod;
				if(i + 1 <= n && 1 <= j && j <= n && !q)
				{
					if(a[i + 1] == -1)
					{
						if(!row[j] && !col[i + 1]) f[i + 1][j][k + 1][1][1] -= f[i][j][k][p][q], f[i + 1][j][k + 1][1][1] %= mod;
					}
					else if(a[i + 1] == j)
					{
						f[i + 1][j][k][1][1] -= f[i][j][k][p][q], f[i + 1][j][k][1][1] %= mod;
					}
				}
			}
			if(j <= n)
			{
				f[i][j + 1][k][p][0] += f[i][j][k][p][q], f[i][j + 1][k][p][0] %= mod;
				if(1 <= i && i <= n && j + 1 <= n && !p)
				{
					if(a[i] == -1)
					{
						if(!row[j + 1] && !col[i]) f[i][j + 1][k + 1][1][1] -= f[i][j][k][p][q], f[i][j + 1][k + 1][1][1] %= mod;
					}
					else if(a[i] == j + 1) f[i][j + 1][k][1][1] -= f[i][j][k][p][q], f[i][j + 1][k][1][1] %= mod;
				}
			}
			
		}
	}
	for(int i = 0; i <= n; i++)
	{
		ans += f[n + 1][n + 1][i][0][0] * fac[n - i] % mod;
		ans %= mod;
	}
	ans = (ans + mod) % mod;
	cout << ans;
	return 0;
}
posted @ 2025-10-27 15:59  心灵震荡  阅读(31)  评论(0)    收藏  举报