2025 CSP-S 模拟赛 14

2025 CSP-S 模拟赛 14

\(\text{Link}\)

得分

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|\) 的贡献。于是有转移:

\[f_u=\text{2ndmax} f_v+|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\) 则表示无限制)。

那么转移方程就是非常显然的了:

\[f(i,j,t,u)=\max(f(i,k-1,v,u)+f(k+1,j,t,v)+\text{calc}(i-1,k,j+1,t)) \]

此时复杂度是 \(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;
}
posted @ 2025-07-10 10:30  UKE_Automation  阅读(132)  评论(0)    收藏  举报