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)\) 的最大权值。发现有以下转移:
两个式子分别对应从上一行走过来,以及从这一行前面走过来的情况。
\(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;
}

浙公网安备 33010602011771号