2025.8.26模拟赛
T1
赛时将 \(n\) 改为 \(2\times 10^5\),值域改为正数。
\(r\) 肯定取 \(l+d\),区间越长肯定越好。
考虑无论如何,\(a_{i},a_{i+1}\) 在绝对值中的计算关系不变,也就是若 \(a_i>a_{i+1}\),永远有 \(a_i\ge a_{i+1}\)。
然后绝对值就消掉了,将加法的 \(a\) 放一起,减法的 \(a\) 放一起,枚举 \(l\)(一定是某个 \(a_i,a_i-d\)),求一个 \(>r,<l\) 的数的数量与和,然后就行了。
赛时代码
#include <bits/stdc++.h>
#define int long long
#define mid (l + r >> 1)
using namespace std;
const int N = 2e5 + 5, M = 6e6;
struct SEG {
int s[M], t[M], ls[M], rs[M], rt, tot;
void upd(int &p, int l, int r, int x) {
if (!p)
p = ++tot;
s[p] += x, ++t[p];
if (l == r)
return;
x <= mid ? upd(ls[p], l, mid, x) : upd(rs[p], mid + 1, r, x);
}
int ask(int p, int l, int r, int L, int R, int o) {
if (L > R || !p)
return 0;
if (L <= l && r <= R)
return o ? s[p] : t[p];
int res = 0;
if (L <= mid)
res += ask(ls[p], l, mid, L, R, o);
if (mid < R)
res += ask(rs[p], mid + 1, r, L, R, o);
return res;
}
} add, era;
int n, m = 1e9 + 5, d, a[N];
vector<int> vec;
int ans, sum;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> d;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
vec.emplace_back(a[i]);
vec.emplace_back(a[i] - d);
}
for (int i = 1; i < n; ++i) {
sum += abs(a[i] - a[i + 1]);
add.upd(add.rt, 1, m, max(a[i], a[i + 1]));
era.upd(era.rt, 1, m, min(a[i], a[i + 1]));
}
sort(vec.begin(), vec.end());
vec.erase(unique(vec.begin(), vec.end()), vec.end());
for (int l : vec) {
int r = l + d;
int d1 = r * add.ask(add.rt, 1, m, r + 1, m, 0) - add.ask(add.rt, 1, m, r + 1, m, 1) - add.ask(add.rt, 1, m, 1, l - 1, 1) + l * add.ask(add.rt, 1, m, 1, l - 1, 0);
int d2 = era.ask(era.rt, 1, m, 1, l - 1, 1) - l * era.ask(era.rt, 1, m, 1, l - 1, 0) - r * era.ask(era.rt, 1, m, r + 1, m, 0) + era.ask(era.rt, 1, m, r + 1, m, 1);
ans = max(ans, sum + d1 + d2);
}
cout << ans << '\n';
return 0;
}
T2
给定一棵有根树,保证标号为一个合法的 dfs 序。把树的所有叶子按照编号顺序取出,设为 \(u_1,u_2,…,u_m\)。额外添加 m 条形如 \((u_i,u_{i+1})\) 的无向边(\(u_{m+1}=u_1\))。求新图的匹配个数,对 \(998244353\) 取模。
匹配:一个边集 \(S\) 满足其所有边的所有端点两两不同。匹配个数即位合法的边集个数(重边算不同的边)。
\(1\le n\le 10^5\)
赛时时间都去想 T3,T4 了,T2 只拿了 \(50\)。
一种是 \(f_{u,0/1,0/1,0/1}\) 表示 \(u\) 子树内 \(u\) 是否已匹配,\(dfn\) 最小的点是否已匹配,\(dfn\) 最大的点是否已匹配。转移较为复杂。
一种是 \(f_{u,0/1,0/1,0/1}\) 表示 \(u\) 子树内 \(u\),\(dfn\) 最小的点,\(dfn\) 最大的点,是否计划匹配,然后再能匹配的时候就一定要匹配。转移较为简单。
倒序枚举结点 \(u\),然后判断是否是 \(u\) 是否是 \(fa_u\) 最右边的儿子转移即可。
赛后代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 5, P = 998244353;
int n, a[N], d[N], fa[N];
int f[N][2][2][2], g[2][2][2], ans;
void Max(int &x, int y) { x = max(x, y); }
void add(int &x, int y) { x = (x + y >= P ? x + y - P : x + y); }
void cov(int u) {
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 2; ++j)
for (int k = 0; k < 2; ++k)
f[u][i][j][k] = g[i][j][k];
memset(g, 0, sizeof(g));
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n, a[1] = 1;
for (int i = 2; i <= n; ++i)
cin >> fa[i], ++d[fa[i]], a[i] = i;
for (int i = n; i >= 2; --i)
Max(a[fa[i]], a[i]);
for (int v = n; v >= 2; --v) {
int u = fa[v];
if (!d[v]) {
f[v][0][0][0] = f[v][1][0][1] = f[v][1][1][0] = 1;
}
if (a[u] == a[v]) {
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 2; ++j) {
f[u][1][i][j] = f[v][0][i][j];
f[u][0][i][j] = (f[v][0][i][j] + f[v][1][i][j]) % P;
}
} else {
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 2; ++j)
for (int k = 0; k < 2; ++k) {
add(g[0][i][j], f[u][0][k][j] * (f[v][0][i][k] + f[v][1][i][k]) % P);
add(g[1][i][j], (f[u][0][k][j] * f[v][0][i][k] % P + f[u][1][k][j] * (f[v][0][i][k] + f[v][1][i][k]) % P) % P);
}
cov(u);
}
}
for (int i = 0; i < 2; ++i)
for (int j = 0; j < 2; ++j)
(ans += f[1][i][j][j]) %= P;
cout << ans << '\n';
return 0;
}
考虑到循环较为不适,直接展开即可。
3k代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 5, P = 998244353;
int n, ans, a[N], d[N], fa[N];
int f[N][2][2][2], g[2][2][2];
void Max(int &x, int y) { x = max(x, y); }
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n, a[1] = 1;
for (int i = 2; i <= n; ++i)
cin >> fa[i], ++d[fa[i]], a[i] = i;
for (int i = n; i >= 2; --i)
a[fa[i]] = max(a[fa[i]], a[i]), !d[i] ? f[i][0][0][0] = f[i][1][0][1] = f[i][1][1][0] = 1 : 1;
for (int v = n, u = fa[v]; v >= 2; --v, u = fa[v])
if (a[u] == a[v]) {
f[u][1][0][0] = f[v][0][0][0], f[u][1][0][1] = f[v][0][0][1];
f[u][1][1][0] = f[v][0][1][0], f[u][1][1][1] = f[v][0][1][1];
f[u][0][0][0] = (f[v][0][0][0] + f[v][1][0][0]) % P;
f[u][0][0][1] = (f[v][0][0][1] + f[v][1][0][1]) % P;
f[u][0][1][0] = (f[v][0][1][0] + f[v][1][1][0]) % P;
f[u][0][1][1] = (f[v][0][1][1] + f[v][1][1][1]) % P;
} else {
g[0][0][0] = (f[u][0][0][0] * f[v][0][0][0] + f[u][0][0][0] * f[v][1][0][0] + f[u][0][1][0] * f[v][0][0][1] + f[u][0][1][0] * f[v][1][0][1]) % P;
g[0][0][1] = (f[u][0][0][1] * f[v][0][0][0] + f[u][0][0][1] * f[v][1][0][0] + f[u][0][1][1] * f[v][0][0][1] + f[u][0][1][1] * f[v][1][0][1]) % P;
g[0][1][0] = (f[u][0][0][0] * f[v][0][1][0] + f[u][0][0][0] * f[v][1][1][0] + f[u][0][1][0] * f[v][0][1][1] + f[u][0][1][0] * f[v][1][1][1]) % P;
g[0][1][1] = (f[u][0][0][1] * f[v][0][1][0] + f[u][0][0][1] * f[v][1][1][0] + f[u][0][1][1] * f[v][0][1][1] + f[u][0][1][1] * f[v][1][1][1]) % P;
g[1][0][0] = (f[u][0][0][0] * f[v][0][0][0] + f[u][1][0][0] * f[v][0][0][0] + f[u][1][0][0] * f[v][1][0][0] + f[u][0][1][0] * f[v][0][0][1] + f[u][1][1][0] * f[v][0][0][1] + f[u][1][1][0] * f[v][1][0][1]) % P;
g[1][0][1] = (f[u][0][0][1] * f[v][0][0][0] + f[u][1][0][1] * f[v][0][0][0] + f[u][1][0][1] * f[v][1][0][0] + f[u][0][1][1] * f[v][0][0][1] + f[u][1][1][1] * f[v][0][0][1] + f[u][1][1][1] * f[v][1][0][1]) % P;
g[1][1][0] = (f[u][0][0][0] * f[v][0][1][0] + f[u][1][0][0] * f[v][0][1][0] + f[u][1][0][0] * f[v][1][1][0] + f[u][0][1][0] * f[v][0][1][1] + f[u][1][1][0] * f[v][0][1][1] + f[u][1][1][0] * f[v][1][1][1]) % P;
g[1][1][1] = (f[u][0][0][1] * f[v][0][1][0] + f[u][1][0][1] * f[v][0][1][0] + f[u][1][0][1] * f[v][1][1][0] + f[u][0][1][1] * f[v][0][1][1] + f[u][1][1][1] * f[v][0][1][1] + f[u][1][1][1] * f[v][1][1][1]) % P;
f[u][0][0][0] = g[0][0][0], f[u][0][0][1] = g[0][0][1], f[u][0][1][0] = g[0][1][0], f[u][0][1][1] = g[0][1][1];
f[u][1][0][0] = g[1][0][0], f[u][1][0][1] = g[1][0][1], f[u][1][1][0] = g[1][1][0], f[u][1][1][1] = g[1][1][1];
}
ans = (f[1][0][0][0] + f[1][0][1][1] + f[1][1][0][0] + f[1][1][1][1]) % P, cout << ans << '\n';
return 0;
}
T3
有一张 \(2\times n\) 的网格图,你要从左上角 \((1,1)\) 走到右下角 \((2,n)\)。每条边有边权,并且有额外的 \(m\) 条限制。每条限制形如:给定 \(i,j,c\),如果你同时走了 \((1,i)\) 到 \((1,i+1)\) 和 \((2,j)\) 到 \((2,j+1)\) 这两条边,那么你就需要额外 c 的代价。求走过的边权和加上代价的最小值。
\(1\le n\le 500,1\le m\le 1000\)。
这个玩意发现 dp 贡献难算,所以不是 dp。
这个玩意发现最小费用最大流需要完成一个“且”的逻辑运算,所以不是最小费用最大流。
但是发现网格上下走费用可以转化为如果同时走了 \((1/2,i)\to (1/2,i+1)\) 和 \((2/1,i+1)\to (2/1,i+2)\) 两条边的代价。考虑构造一个最小割。
显然我们要从每对上下两条边选一个,考虑 \(S\) 连所有 \(i\),代价为 \((2,i)\to (2,i+1)\) 的边权,所有 \(i\)连 \(T\),代价为 \((1,i)\to (1,i+1)\) 的边权。之间是网格上下走的费用,这样发现如果相邻两个边没有被割在同一个集合就需要再花费这个边的代价来割。
然后另 \(m\) 条限制类似。
网络流还是太菜了,赛时花了 INF 的时间。
赛时代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 505, M = 1e4 + 5;
const int INF = 1e17;
int n, m, s, t;
int head[N], tot = 1;
struct edge {
int v, f, nxt;
} e[M];
inline void add(int u, int v, int f) {
e[++tot] = (edge){v, f, head[u]}, head[u] = tot;
e[++tot] = (edge){u, 0, head[v]}, head[v] = tot;
}
int dis[N], now[N];
bool bfs() {
for (int i = 0; i <= t; ++i)
dis[i] = INF;
queue<int> q;
q.push(s);
dis[s] = 0;
now[s] = head[s];
while (q.size()) {
int u = q.front();
q.pop();
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].v, f = e[i].f;
if (f <= 0 || dis[v] != INF)
continue;
q.push(v);
now[v] = head[v];
dis[v] = dis[u] + 1;
if (v == t)
return 1;
}
}
return 0;
}
int dfs(int u, int sum) {
if (u == t)
return sum;
int ans = 0;
for (int i = now[u]; i && sum; i = e[i].nxt) {
now[u] = i;
int v = e[i].v;
if (e[i].f > 0 && dis[v] == dis[u] + 1) {
int k = dfs(v, min(sum, e[i].f));
if (k == 0)
dis[v] = INF;
e[i].f -= k, e[i ^ 1].f += k;
ans += k, sum -= k;
}
}
return ans;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
s = 0, t = n;
int ans = 0;
for (int i = 1, x; i < n; ++i)
cin >> x, add(i, t, x);
for (int i = 1, x; i <= n; ++i) {
cin >> x;
add(i - 1, i, x);
if (i > 1)
add(i, i - 1, x);
}
for (int i = 1, x; i < n; ++i)
cin >> x, add(s, i, x);
for (int i = 1, x, y, z; i <= m; ++i)
cin >> x >> y >> z, add(x, y, z);
while (bfs())
ans += dfs(s, INF);
cout << ans << '\n';
return 0;
}
T4
给定 \(n,m\),对所有长度为 \(n\),值域为 \([1,m]\) 的序列求众数出现次数之和。对 \(10^9+7\) 取模。多组数据。
50pts:\(n\le 100,m\le 10^9\)。
100pts:\(n\le 10^3,m\le 10^9\)。
枚举众数个数,众数出现的次数,这些方案是好算的,然后要解决一个长度为 \(n\) 的序列,值域为 \([1,m]\),要求每个数字出现的次数 \(\le c\) 的个数。即我们要求
枚举每个 \(a_i\),然后会发现要做 \(m\) 次卷积,并不用 FFT,只需要倍增即可获得 50pts。
赛时 50pts 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5, P = 1e9 + 7;
int fac[N], inv[N], vni[N];
int f[N], g[N], h[N];
void add(int &x, int y) { x = (x + y >= P ? x + y - P : x + y); }
int qp(int x, int y) {
int z = 1;
for (; y; y >>= 1, x = 1ll * x * x % P)
z = 1ll * z * ((y & 1) ? x : 1) % P;
return z;
}
int C(int n, int m) {
if (m > n || n < 0 || m < 0)
return 0;
m = min(m, n - m);
if (n <= 200000)
return 1ll * fac[n] * inv[m] % P * inv[n - m] % P;
int res = 1;
for (int i = 1; i <= m; ++i)
res = 1ll * res * (n - i + 1) % P * vni[i] % P;
return res;
}
int calc(int n, int m, int c) {
if (m < 0)
return 0;
for (int i = 0; i <= n; ++i)
f[i] = g[i] = 0;
c = min(c, n);
for (int i = 0; i <= c; ++i)
g[i] = inv[i];
f[0] = 1;
for (; m; m >>= 1) {
if (m & 1) {
for (int i = n; i >= 0; --i)
for (int j = n - i; j >= 0; --j)
add(h[i + j], 1ll * f[i] * g[j] % P);
for (int i = 0; i <= n; ++i)
f[i] = h[i], h[i] = 0;
}
for (int i = n; i >= 0; --i)
for (int j = n - i; j >= 0; --j)
add(h[i + j], 1ll * g[i] * g[j] % P);
for (int i = 0; i <= n; ++i)
g[i] = h[i], h[i] = 0;
}
return 1ll * fac[n] * f[n] % P;
}
void init() {
const int n = 2e5;
fac[0] = inv[0] = 1;
for (int i = 1; i <= n; ++i) {
fac[i] = 1ll * fac[i - 1] * i % P;
inv[i] = qp(fac[i], P - 2);
vni[i] = qp(i, P - 2);
}
}
int T, n, m, ans;
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; ++i)
for (int j = 1; i * j <= n; ++j)
add(ans, 1ll * C(m, i) * fac[n] % P * inv[n - i * j] % P * qp(inv[j], i) % P * calc(n - i * j, m - i, j - 1) % P * j % P);
cout << ans << '\n', ans = 0;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
init();
for (cin >> T; T; --T)
solve();
return 0;
}
然后就是一堆“可以发现”的生成函数性质,结论,不会。

浙公网安备 33010602011771号