2025 CSP-S 模拟赛 14
2025 CSP-S 模拟赛 14
得分
| T1 | T2 | T3 | T4 | Sum | Rank |
|---|---|---|---|---|---|
| \(100\) | \(0(17)\) | \(10\) | \(20\) | \(130(147)\) | \(2/20\) |
题解
T1 魔力屏障
这个 T1 不知道为什么在场上很难做,但是实际上是简单的。
手玩样例发现贪心策略全部假掉,考虑 dp。我们在一个屏障前放置魔力一定是使魔力放满,不然不会更优。令 \(dp(l,r,k)\) 表示消除区间 \([l,r]\) 所有屏障后,剩下魔力值为 \(k\) 的最小花费。枚举断点分两种情况转移即可,复杂度 \(O(n^3V^2)\),由于常数较小所以可以通过。
#include <bits/stdc++.h>
#define il inline
#define pii pair<int, int>
#define mk make_pair
#define fi first
#define se second
using namespace std;
const int Maxn = 2e5 + 5;
const int Inf = 1e9;
template <typename T> il void chkmax(T &x, T y) {x = (x >= y ? x : y);}
template <typename T> il void chkmin(T &x, T y) {x = (x <= y ? x : y);}
template <typename T>
il void read(T &x) {
x = 0; char ch = getchar(); bool flg = 0;
for(; ch < '0' || ch > '9'; ch = getchar()) flg = (ch == '-');
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
flg ? x = -x : 0;
}
template <typename T>
il void write(T x, bool typ = 1) {
static short Stk[50], Top = 0;
x < 0 ? putchar('-'), x = -x : 0;
do Stk[++Top] = x % 10, x /= 10; while(x);
while(Top) putchar(Stk[Top--] | 48);
typ ? putchar('\n') : putchar(' ');
}
il void IOS() {ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);}
il void File() {freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout);}
bool Beg;
int n, m, a[Maxn];
int dp[75][75][155];
int f[75][155], g[155][75];
bool End;
il void Usd() {cerr << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n"; }
int main() {
// freopen("magic4.in", "r", stdin);
// freopen("my.out", "w", stdout);
read(n);
for(int i = 1; i <= n; i++) read(a[i]), chkmax(m, a[i]);
for(int i = 1; i <= n; i++) {
for(int j = 0; j <= m; j++) {
int now = j;
for(int k = i; k <= n; k++) {
if(now >= a[k]) now >>= 1;
else break;
f[i][j] = k;
}
}
}
for(int i = 0; i <= m; i++) {
int now = i;
for(int j = 1; j <= n; j++) {
now >>= 1; g[i][j] = now;
}
}
for(int i = 1; i <= n; i++) for(int j = 0; j <= m; j++) dp[i][i][j] = Inf;
for(int i = 1; i <= n; i++) {
for(int j = m; j >= a[i]; j--) dp[i][i][j >> 1] = j;
}
for(int l = 2; l <= n; l++) {
for(int i = 1; i + l - 1 <= n; i++) {
int j = i + l - 1;
for(int v = 0; v <= m; v++) dp[i][j][v] = Inf;
for(int k = i; k < j; k++) {
for(int _v = 0; _v < a[k + 1]; _v++) {
if(dp[i][k][_v] == Inf) continue;
for(int v = 0; v <= m; v++) {
chkmin(dp[i][j][v], dp[i][k][_v] + dp[k + 1][j][v] - _v);
}
}
}
for(int k = i; k < j; k++) {
for(int _v = 0; _v <= m; _v++) {
if(dp[i][k][_v] == Inf) continue;
for(int v = _v; v <= m; v++) {
chkmin(dp[i][j][v], dp[i][k][_v] + dp[k + 1][j][v - _v]);
}
}
}
for(int k = i; k < j; k++) {
for(int _v = 0; _v <= m; _v++) {
if(f[k + 1][_v] >= j) {
int v = g[_v][j - k];
chkmin(dp[i][j][v], dp[i][k][_v]);
}
}
}
}
}
for(int i = 1; i <= n; i++) {
int ans = Inf;
for(int j = 0; j <= m; j++) {
chkmin(ans, dp[1][i][j]);
}
write(ans, 0);
}
puts("");
Usd();
return 0;
}
T2 诡秘之主
场上做了 1.5h 基本想出来了个大概,但是 T1 做太久了导致写不出来。。
考虑 \(0\) 最后会形成一条挂在左边的链,所以枚举区间左端点,分 \(0\) 的个数进行讨论:
- \(cnt=0\),此时只有 \(1\),则此时树高是容易直接求出的。我们在这里将所有贡献拆成左端点固定在某个点,右端点在一段区间内,对答案贡献均为 \(v\) 的形式,然后我们做一次离线扫描线即可求出答案。那么这样的话我们枚举树高 \(h\),计算出此时 \(1\) 的个数的上下界,即可得出右端点对应区间。
- \(cnt\le \log n\),与上面类似,枚举树高 \(h\),计算出此时右端点对应区间即可。具体式子可以自己推一下。
- \(cnt>\log n\),此时答案直接就是 \(cnt\) 或者 \(cnt-1\),这需要看开头是 \(0\) 还是 \(1\)。我们依然从右到左扫描线,在当前点容易求出每个右端点的对应贡献,那么用线段树做一下历史和即可。用别的方式维护也是可以的。
#include <bits/stdc++.h>
#define il inline
#define int long long
using namespace std;
const int Maxn = 2e5 + 5;
const int Inf = 2e9;
const int Mod = 998244353;
il int Add(int x, int y) {return x + y >= Mod ? x + y - Mod: x + y;} il void pls(int &x, int y) {x = Add(x, y);}
il int Del(int x, int y) {return x - y < 0 ? x - y + Mod : x - y;} il void sub(int &x, int y) {x = Del(x, y);}
il int qpow(int a, int b, int P = Mod) {int res = 1; for(; b; a = 1ll * a * a % P, b >>= 1) if(b & 1) res = 1ll * res * a % P; return res;}
il int Inv(int a) {return qpow(a, Mod - 2);}
template <typename T> il void chkmax(T &x, T y) {x = (x >= y ? x : y);}
template <typename T> il void chkmin(T &x, T y) {x = (x <= y ? x : y);}
il void IOS() {ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);}
il void File() {freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout);}
bool Beg;
int n, m, a[Maxn], sum[Maxn];
int pos[Maxn], cnt;
string S;
struct Dat1 {int r, id;};
vector <Dat1> V[Maxn];
struct Dat2 {int l, r, v;};
vector <Dat2> C[Maxn];
il void work(int x) {
int p = lower_bound(pos + 1, pos + cnt + 1, x) - pos;
int b = pos[p] - x;
C[x].push_back({x, x, 0});
int tot = 0, num0 = 0, now = pos[p] - 1;
// 层数 0 个数 当前区间
if(b >= 1 && x != pos[p] - 1) {
for(int h = 1; h <= 17; h++) {
int l = (1 << h), r = (1 << h + 1) - 1;
l = x + l - 1, r = min(x + r - 1, pos[p] - 1);
C[x].push_back({l, r, h});
if(r == pos[p] - 1) {tot = h; break;}
}
}
for(int i = p; i <= min(cnt, p + 16); i++) {
num0++;
for(int j = tot; j <= 18; j++) {
if(j < num0 - 1) continue;
if(b > 0 && j < num0) continue;
if((1 << j + 1) - (1 << num0) < b) continue;
int mx = (1 << j + 1) - (1 << j - min(b, j - num0 + 1)) - 1;
int l = now + 1, r = min(pos[i + 1] - 1, x + mx - 1 + num0);
if(l > r) continue;
C[x].push_back({l, r, j});
now = r;
if(now == pos[i + 1] - 1) {tot = j; break;}
}
}
}
int ans[Maxn];
namespace SGT {
struct node {
int sum;
int addk, addb, smx, len;
}t[Maxn << 2];
#define ls(p) (p << 1)
#define rs(p) (p << 1 | 1)
il void pushup(int p) {
t[p].smx = t[ls(p)].smx + t[rs(p)].smx;
t[p].sum = t[ls(p)].sum + t[rs(p)].sum;
t[p].len = t[ls(p)].len + t[rs(p)].len;
}
il void build(int p, int l, int r) {
if(l == r) {
t[p].smx = sum[l];
t[p].len = 1;
return ;
}
int mid = (l + r) >> 1;
build(ls(p), l, mid), build(rs(p), mid + 1, r);
pushup(p);
}
il void pushk(int p, int v) {t[p].addk += v, t[p].sum += t[p].smx * v;}
il void pushb(int p, int v) {t[p].addb += v, t[p].sum += t[p].len * v;}
il void pushdown(int p) {
pushk(ls(p), t[p].addk), pushk(rs(p), t[p].addk), t[p].addk = 0;
pushb(ls(p), t[p].addb), pushb(rs(p), t[p].addb), t[p].addb = 0;
}
il void mdf(int p, int l, int r, int pl, int pr, int k, int b) {
if(pl > pr) return ;
if(pl <= l && r <= pr) {
pushk(p, k), pushb(p, b);
return ;
}
pushdown(p);
int mid = (l + r) >> 1;
if(pl <= mid) mdf(ls(p), l, mid, pl, pr, k, b);
if(pr > mid) mdf(rs(p), mid + 1, r, pl, pr, k, b);
pushup(p);
}
il int query(int p, int l, int r, int pl, int pr) {
if(pl > pr) return 0;
if(pl <= l && r <= pr) return t[p].sum;
pushdown(p);
int mid = (l + r) >> 1, res = 0;
if(pl <= mid) res += query(ls(p), l, mid, pl, pr);
if(pr > mid) res += query(rs(p), mid + 1, r, pl, pr);
return res;
}
}
namespace BIT {
int c1[Maxn], c2[Maxn];
il int lowbit(int x) {return x & (-x);}
il void mdf(int l, int r, int v) {
for(int i = l; i <= n; i += lowbit(i)) c1[i] += v, c2[i] += v * l;
for(int i = r + 1; i <= n; i += lowbit(i)) c1[i] -= v, c2[i] -= v * (r + 1);
}
il int query(int x) {
int sum = 0;
for(int i = x; i; i -= lowbit(i)) sum += c1[i] * (x + 1) - c2[i];
return sum;
}
il int query(int l, int r) {return query(r) - query(l - 1);}
}
bool End;
il void Usd() {cerr << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n"; }
signed main() {
IOS();
cin >> n >> m >> S;
for(int i = 1; i <= n; i++) {
a[i] = S[i - 1] - '0';
if(a[i] == 0) pos[++cnt] = i;
sum[i] = sum[i - 1] + (a[i] == 0);
}
pos[cnt + 1] = n + 1;
for(int i = 1, l, r; i <= m; i++) {
cin >> l >> r;
V[l].push_back({r, i});
}
for(int i = 1; i <= n; i++) work(i);
SGT::build(1, 1, n);
for(int i = n; i >= 1; i--) {
int t = lower_bound(pos + 1, pos + cnt + 1, i) - pos;
if(t + 17 > cnt) continue;
int p = pos[t + 17];
SGT::mdf(1, 1, n, p, n, 1, -sum[i]);
for(auto p : V[i]) pls(ans[p.id], SGT::query(1, 1, n, i, p.r));
}
for(int i = n; i >= 1; i--) {
for(auto p : C[i]) BIT::mdf(p.l, p.r, p.v);
for(auto p : V[i]) pls(ans[p.id], BIT::query(i, p.r));
}
for(int i = 1; i <= m; i++) cout << ans[i] % Mod << '\n';
Usd();
return 0;
}
T3 博弈
首先考虑 \(S,T\) 相连的情况,此时小 N 会不断往下走直到走到一个叶子节点,此时它无法再行动了。那么小 Y 此时只需要回复该节点到 \(S\) 的所有边,并剪去所有的旁支即可。
设 \(f_u\) 表示当前小 N 在 \(u\),最终小 N 被迫回到 \(u\) 所需的操作次数。在每个点小 Y 都可以选择剪去一个子树,那么小 N 只能走到 \(f_v\) 中第二大的那个子树。然后返回的时候我们需要删去所有旁支,并且恢复 \((v,u)\) 这条边,所以还需要 \(|son_u|\) 的贡献。于是有转移:
考虑一般情况,需要想到的是此时双方的博弈过程极其难确定,因为当前小 N 既可以向下也可以向上。考虑转化为判定性问题。令 \(T\) 为根,计算 \(g_u\) 表示将 \(u\) 到根的所有旁支剪掉的操作次数。二分答案 \(mid\),初始时如果存在 \(g_S+f_v+1>mid\),那么这个儿子必须剪掉,否则无解。
我们从 \(S\to T\) 不断执行这个过程,如果有不合法的儿子就剪掉,如果当前操作次数大于可操作次数就返回无解即可。这样的话可以做到 \(O(n\log n)\),可以通过。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int Maxn = 2e6 + 5;
const int Inf = 2e9;
template <typename T> il void chkmax(T &x, T y) {x = (x >= y ? x : y);}
template <typename T> il void chkmin(T &x, T y) {x = (x <= y ? x : y);}
template <typename T>
il void read(T &x) {
x = 0; char ch = getchar(); bool flg = 0;
for(; ch < '0' || ch > '9'; ch = getchar()) flg = (ch == '-');
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
flg ? x = -x : 0;
}
template <typename T>
il void write(T x, bool typ = 1) {
static short Stk[50], Top = 0;
x < 0 ? putchar('-'), x = -x : 0;
do Stk[++Top] = x % 10, x /= 10; while(x);
while(Top) putchar(Stk[Top--] | 48);
typ ? putchar('\n') : putchar(' ');
}
il void IOS() {ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);}
il void File() {freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout);}
bool Beg;
int n, T, S;
int head[Maxn], edgenum;
struct node {
int nxt, to;
}edge[Maxn];
il void add(int u, int v) {
edge[++edgenum] = {head[u], v}; head[u] = edgenum;
edge[++edgenum] = {head[v], u}; head[v] = edgenum;
}
int fa[Maxn], son[Maxn], deg[Maxn];
int f[Maxn], g[Maxn];
il void dfs(int x, int fth) {
fa[x] = fth;
int mx = 0, lmx = 0;
int son = deg[x];
if(fth) son--;
if(fth) g[x] = g[fth] + son - 1;
for(int i = head[x]; i; i = edge[i].nxt) {
int to = edge[i].to;
if(to == fth) continue;
dfs(to, x);
if(f[to] > mx) lmx = mx, mx = f[to];
else if(f[to] > lmx) lmx = f[to];
}
f[x] = lmx + son;
}
il int check(int mid) {
int u = S, lst = -1, cnt = 0, ret = 0;
while(u != T) {
int now = cnt;
ret++;
for(int i = head[u]; i; i = edge[i].nxt) {
int to = edge[i].to;
if(to == fa[u] || to == lst) continue;
if(f[to] + g[u] + now + (lst == -1) > mid) cnt++;
}
if(cnt > mid || cnt > ret) return 0;
lst = u; u = fa[u];
}
return 1;
}
bool End;
il void Usd() {cerr << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n"; }
int main() {
read(n), read(T), read(S);
for(int i = 1, u, v; i < n; i++) {
read(u), read(v);
add(u, v); deg[u]++, deg[v]++;
}
dfs(T, 0);
int l = 0, r = (n << 1), res = 0;
while(l <= r) {
int mid = (l + r) >> 1;
if(check(mid)) r = mid - 1, res = mid;
else l = mid + 1;
}
write(res);
Usd();
return 0;
}
T4 地雷
首先肯定考虑区间 dp,基础状态是 \([i,j]\) 表示区间,转移的时候我们需要枚举 \(k\) 表示区间中最晚删去的数字。然后由于我们知道 \(k\) 右边第二个数,所以再设 \(t\) 表示 \(j+1\) 之后第一个比 \([i,j]\) 更晚删除的数作为这个数。但是转移的时候我们需要保证左区间对应的 \(t\) 在右区间中是一个前缀最晚消失数,所以再设一个 \(u\),表示钦定 \(u\) 是前缀最晚删除的数(若 \(u=i-1\) 则表示无限制)。
那么转移方程就是非常显然的了:
此时复杂度是 \(O(n^6)\) 的,不过常数非常小,所以可以直接通过。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int Maxn = 70 + 5;
const int Inf = 2e9;
template <typename T> il void chkmax(T &x, T y) {x = (x >= y ? x : y);}
template <typename T> il void chkmin(T &x, T y) {x = (x <= y ? x : y);}
template <typename T>
il void read(T &x) {
x = 0; char ch = getchar(); bool flg = 0;
for(; ch < '0' || ch > '9'; ch = getchar()) flg = (ch == '-');
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
flg ? x = -x : 0;
}
template <typename T>
il void write(T x, bool typ = 1) {
static short Stk[50], Top = 0;
x < 0 ? putchar('-'), x = -x : 0;
do Stk[++Top] = x % 10, x /= 10; while(x);
while(Top) putchar(Stk[Top--] | 48);
typ ? putchar('\n') : putchar(' ');
}
il void IOS() {ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);}
il void File() {freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout);}
bool Beg;
int n, p[Maxn], q[Maxn], r[Maxn], s[Maxn];
int dp[Maxn][Maxn][Maxn][Maxn];
il int sqr(int x) {return x * x;}
il int calc(int i, int j, int k, int l) {
return sqr(p[i] - q[j]) + sqr(p[j] - r[k]) + sqr(p[k] - s[l]);
}
bool End;
il void Usd() {cerr << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n"; }
int main() {
read(n);
for(int i = 1; i <= n; i++) read(p[i]);
for(int i = 1; i <= n; i++) read(q[i]);
for(int i = 1; i <= n; i++) read(r[i]);
for(int i = 1; i <= n; i++) read(s[i]);
memset(dp, 128, sizeof dp);
for(int i = 1; i <= n + 1; i++) {
dp[i][i - 1][i - 1][i] = 0;
for(int j = i + 1; j <= n + 2; j++) {
if(j > i + 1) dp[i][i][i][j] = dp[i][i][i - 1][j] = calc(i - 1, i, i + 1, j);
dp[i][i - 1][i - 1][j] = 0;
}
}
for(int l = 2; l <= n; l++) {
for(int i = 1; i + l - 1 <= n; i++) {
int j = i + l - 1;
for(int u = i - 1; u <= j; u++) {
for(int t = j + 2; t <= n + 2; t++) {
for(int k = max(i, u); k <= j; k++) {
for(int v = k + 1; v <= j + 1; v++) {
int c1 = (u == k) ? i - 1 : u;
int c2 = (v == j + 1) ? k : v;
chkmax(dp[i][j][u][t], dp[i][k - 1][c1][v] + dp[k + 1][j][c2][t] + calc(i - 1, k, j + 1, t));
}
}
}
}
}
}
write(dp[1][n][0][n + 2]);
Usd();
return 0;
}

浙公网安备 33010602011771号