2025 CSP-S 模拟赛 10
2025 CSP-S 模拟赛 10
得分
| T1 | T2 | T3 | T4 | Sum | Rank |
|---|---|---|---|---|---|
| \(100\) | \(95(100)\) | \(75\) | \(10\) | \(280(285)\) | \(1/14\) |
题解
T1 返乡
先考虑两维的情况,发现第一列上从小到大,第二列上从大到小放最优。扩展到三维情况,把前两个数打包,按照和分成不同的组,则不同组之间会存在偏序关系。第一列按照组的和从小到大放,第二列从大到小放数字即可。
用你喜欢的贪心放出第一列的组即可。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int Maxn = 2e5 + 5;
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;
struct node {int a, b, c;};
vector <node> V;
bool End;
il void Usd() {cerr << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n"; }
int main() {
cin >> n;
int mid = n, l = n / 2, r = n - l;
int num = n;
for(int i = mid - l; i <= mid + r; i++) {
for(int j = max(0, i - n); j <= min(i, n); j++) V.push_back({j, i - j, num});
num--;
}
cout << V.size() << '\n';
for(auto p : V) cout << p.a << " " << p.b << " " << p.c << '\n';
Usd();
return 0;
}
T2 连接
先观察性质:最多只会有一段钢管被切断。同时如果有钢管被切断则最终质量必为 \(L\) 或 \(R\)。
先考虑有钢管被切断的情况,此时质量必定是 \(L,R\)。枚举完整端点的位置,二分求解即可。复杂度 \(O(n\log n)\)。
否则答案只可能是一段完整钢管切下来构成的,也就是 \(\tfrac{\sum m_i}{\sum l_i}\),很显然这是一个分数规划的形式,二分答案,转化为判断 \(\sum m_i-l_i\times mid\ge 0\)。枚举一个端点,另一个端点在一个区间里,我们需要求出该区间中前缀和的最大值。没脑子的话可以大力 ST 表做到 \(O(n\log n\log V)\),有点脑子的话会发现这个区间随着左端点递增而单调向右平移,所以可以用单调队列做到 \(O(n\log V)\)。
考场上没有脑子:
#include <bits/stdc++.h>
#define il inline
#define int long long
#define ll __int128
using namespace std;
typedef long double db;
const int Maxn = 3e5 + 5;
const db Inf = 5e22;
const db eps = 1e-8;
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, L, R;
int v[Maxn], p[Maxn], m[Maxn];
int sm[Maxn], sv[Maxn];
db ans;
il void solve1() {
for(int i = 1; i <= n; i++) m[i] = v[i] * p[i];
for(int i = 1; i <= n; i++) sm[i] = sm[i - 1] + m[i], sv[i] = sv[i - 1] + v[i];
for(int i = 1; i <= n; i++) {
int j = lower_bound(sm + 1, sm + n + 1, L + sm[i - 1]) - sm;
if(sm[j] - sm[i - 1] >= L) {
db A = L * p[j], B = L - (sm[j - 1] - sm[i - 1]) + 1.0 * (sv[j - 1] - sv[i - 1]) * p[j];
chkmax(ans, A / B);
}
}
for(int i = 1; i <= n; i++) {
int j = lower_bound(sm + 1, sm + n + 1, R + sm[i - 1]) - sm;
if(sm[j] - sm[i - 1] >= L) {
db A = R * p[j], B = R - (sm[j - 1] - sm[i - 1]) + 1.0 * (sv[j - 1] - sv[i - 1]) * p[j];
chkmax(ans, A / B);
}
}
}
db num[Maxn], sum[Maxn];
namespace ST {
db mx[19][Maxn];
il void init() {
for(int i = 1; i <= n; i++) mx[0][i] = sum[i];
for(int i = 1; i <= 18; i++) {
for(int j = 1; j + (1 << i) - 1 <= n; j++) {
mx[i][j] = max(mx[i - 1][j], mx[i - 1][j + (1 << (i - 1))]);
}
}
}
il db query(int l, int r) {
if(l > r) return -Inf;
int k = __lg(r - l + 1);
return max(mx[k][l], mx[k][r - (1 << k) + 1]);
}
}
il int check(db mid) {
for(int i = 1; i <= n; i++) num[i] = m[i] - mid * v[i], sum[i] = sum[i - 1] + num[i];
ST::init();
for(int i = 1; i <= n; i++) {
int l = lower_bound(sm + 1, sm + n + 1, L + sm[i - 1]) - sm;
int r = upper_bound(sm + 1, sm + n + 1, R + sm[i - 1]) - sm - 1;
db mx = ST::query(l, r);
if(mx - sum[i - 1] > eps) return 1;
}
return 0;
}
il void solve2() {
db l = 0, r = 1e6, res = 0;
while(r - l > eps) {
db mid = (l + r) / 2;
if(check(mid)) res = mid, l = mid;
else r = mid;
}
chkmax(ans, res);
}
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 >> L >> R;
for(int i = 1; i <= n; i++) cin >> v[i];
for(int i = 1; i <= n; i++) cin >> p[i];
solve1();
solve2();
reverse(v + 1, v + n + 1);
reverse(p + 1, p + n + 1);
solve1();
cout << fixed << setprecision(7) << ans << '\n';
Usd();
return 0;
}
T3 习惯孤独
\(k\le 6\) 肯定考虑状压 dp,我们发现题目的限制其实相当于要求了每次切掉的连通块的大小,所以我们可以记录在当前子树中已经完成了哪些步骤。记 \(f_{i,S}\) 表示在 \(i\) 子树中已经完成了 \(S\) 中步骤的方案数,转移非常容易,做一个类似卷积的状物即可:
然后我们还需要考虑将 \(u\) 子树剩下的部分给切掉的步骤,首先我们可以根据当前的 \(S\) 算出来 \(u\) 子树内还剩多少点,然后找到对应的步骤(可能有多个步骤要求切掉的大小都和这个相等,分别加上即可)。不过值得注意的是切掉 \(u\) 的时间一定晚于 \(S\) 中的任何一步,否则不合法,需要判断。
然后接下来只考虑子树内显然不够,所以再做一遍换根。不过由于我们的转移是一个卷积形式,所以直接去掉当前点对父亲 \(f\) 的贡献是比较困难的。于是我们需要给父亲维护一个 \(pre\) 和 \(suf\) 表示前缀和后缀卷完的结果,相乘即可得出去掉当前点的答案。
不过对每个点都开一个数组空间会炸,所以单次求出一个点的数组时候就要把它所有儿子的信息全部求完。用 BFS 实现可能比较直观。复杂度 \(O(n3^k)\) 或 \(O(n4^k)\),没有卡后者。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int Maxn = 5e3 + 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);}
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, w[Maxn];
int head[Maxn], edgenum;
struct node {
int nxt, to;
}edge[Maxn << 1];
il void add(int u, int v) {
edge[++edgenum] = {head[u], v}; head[u] = edgenum;
edge[++edgenum] = {head[v], u}; head[v] = edgenum;
}
int sm[1 << 6];
int f[Maxn][1 << 6], g[Maxn][1 << 6];
int h[Maxn][1 << 6];
int fa[Maxn], siz[Maxn];
il void dfs(int x, int fth) {
f[x][0] = 1; siz[x] = 1; fa[x] = fth;
for(int i = head[x]; i; i = edge[i].nxt) {
int to = edge[i].to;
if(to == fth) continue;
dfs(to, x); siz[x] += siz[to];
for(int j = 0; j < (1 << m); j++) g[x][j] = f[x][j], f[x][j] = 0;
for(int j = 0; j < (1 << m); j++) {
for(int k = 0; k < (1 << m); k++) {
if(j & k) continue;
pls(f[x][j | k], 1ll * g[x][j] * f[to][k] % Mod);
}
}
}
for(int i = 0; i < (1 << m); i++) g[x][i] = f[x][i];
for(int i = 0; i < (1 << m); i++) {
int sz = siz[x] - sm[i];
for(int j = m; j >= 1; j--) {
if(i >> j - 1 & 1) break;
if(w[j] == sz) pls(f[x][i | (1 << j - 1)], g[x][i]);
}
}
}
queue <int> q;
int pre[Maxn][1 << 6], suf[Maxn][1 << 6];
int son[Maxn], cnt;
il void prepare(int x) {
for(int i = 0; i <= cnt + 1; i++)
for(int j = 0; j < (1 << m); j++) pre[i][j] = suf[i][j] = 0;
cnt = 0;
for(int i = head[x]; i; i = edge[i].nxt) {
int to = edge[i].to;
if(to == fa[x]) continue;
son[++cnt] = to;
}
pre[0][0] = suf[cnt + 1][0] = 1;
for(int i = 1; i <= cnt; i++) {
for(int j = 0; j < (1 << m); j++) {
for(int k = 0; k < (1 << m); k++) {
if(j & k) continue;
pls(pre[i][j | k], 1ll * pre[i - 1][j] * f[son[i]][k] % Mod);
}
}
}
for(int i = cnt; i >= 1; i--) {
for(int j = 0; j < (1 << m); j++) {
for(int k = 0; k < (1 << m); k++) {
if(j & k) continue;
pls(suf[i][j | k], 1ll * suf[i + 1][j] * f[son[i]][k] % Mod);
}
}
}
}
int t[1 << 6];
il void calc(int x, int id) {
for(int i = 0; i < (1 << m); i++) t[i] = 0;
for(int i = 0; i < (1 << m); i++) {
for(int j = 0; j < (1 << m); j++) {
if(i & j) continue;
pls(t[i | j], 1ll * pre[id - 1][i] * suf[id + 1][j] % Mod);
}
}
for(int i = 0; i < (1 << m); i++) {
for(int j = 0; j < (1 << m); j++) {
if(i & j) continue;
pls(h[x][i | j], 1ll * t[i] * h[fa[x]][j] % Mod);
}
}
for(int i = 0; i < (1 << m); i++) t[i] = h[x][i];
int sum = n - siz[x];
for(int i = 0; i < (1 << m); i++) {
int sz = sum - sm[i];
for(int j = m; j >= 1; j--) {
if(i >> j - 1 & 1) break;
if(w[j] == sz) pls(t[i | (1 << j - 1)], h[x][i]);
}
}
for(int i = 0; i < (1 << m); i++) h[x][i] = t[i];
}
il void bfs() {
q.push(1);
while(!q.empty()) {
int x = q.front(); q.pop();
prepare(x); int id = 0;
for(int i = head[x]; i; i = edge[i].nxt) {
if(edge[i].to == fa[x]) continue;
id++;
calc(edge[i].to, id);
q.push(edge[i].to);
}
}
}
int dp[Maxn][1 << 6];
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, u, v; i < n; i++) {
read(u), read(v);
add(u, v);
}
read(m); w[0] = n;
for(int i = 1; i <= m; i++) read(w[i]);
for(int i = m + 1; i >= 1; i--) w[i] = w[i - 1] - w[i];
for(int i = 0; i < (1 << m); i++) {
for(int j = 1; j <= m; j++) {
if(i >> j - 1 & 1) sm[i] += w[j];
}
}
dfs(1, 0);
h[1][0] = 1;
bfs();
int ans = 0;
for(int i = 1; i <= n; i++) {
for(int j = 0; j < (1 << m); j++) {
for(int k = 0; k < (1 << m); k++) {
if(j & k) continue;
pls(dp[i][j | k], 1ll * g[i][j] * h[i][k] % Mod);
}
}
pls(ans, dp[i][(1 << m) - 1]);
}
ans = 1ll * ans * Inv(w[m + 1]) % Mod;
write(ans);
Usd();
return 0;
}
T4 车站
首先对于 \(k=1\),不难想到我们可以构造一个最小根向生成树来得出答案。于是我们有一个暴力的想法:枚举选出的 \(k\) 个点,建一个虚点作为根,然后求出这个图的一个最小根向生成树即可。这个可以利用朱刘算法来求解,朴素复杂度 \(O(nm)\),用左偏堆可以优化到 \(O(n\log n)\),所以此时复杂度是 \(O(C_n^k n\log n)\)。
考虑优化,显然问题的关键是搞掉 \(C_n^k\)。那么我们不能枚举这 \(k\) 个点了,于是考虑拆贡献。把贡献拆到每一条边上,考虑哪些情况会使得最小树形图上有这条边。
在朱刘的过程中维护缩点后每个点集的大小。每一次缩点前我们会得出一个基环树森林,然后来统计这些边的答案。对于一条边 \((u,v,w)\),容易想到,只有当终点站全部都不在 \(u\) 的时候,这条边一定在最小树形图中,所以答案为:
然后我们还需要判断无解,这个比较容易。考虑每一次缩点前的图,可能会存在一些没有出边的点集,如果终点站不设在这些点集里那么肯定无解。根据抽屉原理,对于一个大小为 \(p\) 的没有出边的点集,至少需要 \(n-p+1\) 个点才必然会在这里面放一个点。那么当 \(k\le n-p\) 时一定无解。
#include <bits/stdc++.h>
#define pii pair<int, int>
#define mk make_pair
#define fi first
#define se second
#define il inline
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);}
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, k;
int fac[Maxn], inv[Maxn];
il void init() {
fac[0] = 1;
for(int i = 1; i <= n; i++) fac[i] = 1ll * fac[i - 1] * i % Mod;
inv[n] = Inv(fac[n]);
for(int i = n; i >= 1; i--) inv[i - 1] = 1ll * inv[i] * i % Mod;
}
il int C(int n, int m) {
if(n < 0 || m < 0 || n < m) return 0;
return 1ll * fac[n] * inv[m] % Mod * inv[n - m] % Mod;
}
int rt[Maxn];
namespace LH {
int tot = 0;
struct node {
int l, r, d, tag;
pii val;
}t[Maxn];
#define ls(p) t[p].l
#define rs(p) t[p].r
il void pushtag(int p, int v) {t[p].tag += v, t[p].val.fi += v;}
il void pushdown(int p) {
if(ls(p)) pushtag(ls(p), t[p].tag);
if(rs(p)) pushtag(rs(p), t[p].tag);
t[p].tag = 0;
}
il int merge(int x, int y) {
if(!x || !y) return x | y;
if(t[x].val > t[y].val) swap(x, y);
pushdown(x);
rs(x) = merge(rs(x), y);
if(t[rs(x)].d > t[ls(x)].d) swap(ls(x), rs(x));
t[x].d = t[rs(x)].d + 1;
return x;
}
il void push(int x, pii val) {
t[++tot] = {0, 0, 0, 0, val};
rt[x] = merge(rt[x], tot);
}
il void pop(int x) {
pushdown(rt[x]);
rt[x] = merge(ls(rt[x]), rs(rt[x]));
}
il pii top(int x) {
return t[rt[x]].val;
}
il void join(int x, int y) {
rt[x] = merge(rt[x], rt[y]);
}
}
pii out[Maxn];
int fa1[Maxn], fa2[Maxn], siz[Maxn];
// 找环 缩点
il int find1(int x) {return fa1[x] == x ? x : fa1[x] = find1(fa1[x]);}
il int find2(int x) {return fa2[x] == x ? x : fa2[x] = find2(fa2[x]);}
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(m), read(k);
init();
for(int i = 1, u, v, w; i <= m; i++) {
read(u), read(v), read(w);
LH::push(u, mk(w, v));
}
int mn = Inf;
vector <int> nod;
for(int i = 1; i <= n; i++) {
fa1[i] = i, fa2[i] = i, siz[i] = 1;
if(rt[i]) out[i] = LH::top(i), nod.push_back(i);
else mn = 1;
}
int ans = 0, tot = n;
while(nod.size()) {
vector <vector <int>> cir;
for(auto x : nod) {
int w = out[x].fi, v = out[x].se;
pls(ans, 1ll * C(n - siz[find2(x)], k) * w % Mod);
if(find1(x) ^ find1(v)) {
fa1[find1(x)] = find1(v);
}
else {
vector <int> c;
c.push_back(x);
int now = find2(v);
while(now ^ x) {
c.push_back(now);
now = find2(out[now].se);
}
cir.push_back(c);
}
}
vector <int> nxt;
for(vector <int> c : cir) {
++tot;
fa1[tot] = fa2[tot] = tot;
for(auto x : c) {
siz[tot] += siz[find2(x)];
fa1[find1(x)] = tot, fa2[find2(x)] = tot;
}
for(auto x : c) {
while(rt[x]) {
int v = LH::top(x).se;
if(find2(v) == tot) LH::pop(x);
else break;
}
LH::pushtag(rt[x], -out[x].fi);
LH::join(tot, x);
}
if(rt[tot]) {
out[tot] = LH::top(tot);
nxt.push_back(tot);
}
else chkmin(mn, siz[tot]);
}
nod = nxt;
}
if(k <= n - mn) write(-1), exit(0);
ans = 1ll * ans * Inv(C(n, k)) % Mod;
write(ans);
Usd();
return 0;
}

浙公网安备 33010602011771号