国庆自主考试总结
分了两天,是两次省选考试。
2026.10.6
估:100 + 100 + 0
实:100 + 100 + 0
为什么都会 t3 的 30 pts。
perm
考虑容斥,设 \(g_i\) 表示 \(C\) 确定有 \(i\) 个位置不满足条件其他位置不管的方案数,那么显然有答案:
现在考虑计算 \(g_i\)。注意到 \(A\) 和 \(B\) 都是排列,我们将所有 \((A_i, B_i)\) 建边,那么不难发现形成的图是一堆环,每一条边都是一个位置,若位置 \(i\) 要不满足条件,那么其值一定要对应两个端点中的一个。设 \(f_{i, j}\) 表示长度为 \(i\) 的环钦定了 \(j\) 条边值为两端点中的一个,那么既可以求出图中所有环,背包的算 \(g\)。考虑算 \(f\),考虑由链推过来,设 \(dp_{i, j, 0/1, 0/1}\) 表示一个有 \(i\) 条边的链,钦定了 \(j\) 条边不合法,第一条边的值选的 左端点 或 右端点或两者皆不是,最后一条边选的 左端点或两者皆不是 或 右端点,初始状态显然枚举第一条边的情况,有 \(dp_{1, 0, 1, 0} = 1 \land dp_{1, 1, 0, 0} = 1 \land dp_{1, 1, 1, 1} = 1\)。考虑转移,考虑从 \(dp_{i, j, t, 0/1}\) 转移出去,第 \(i + 1\) 条边不钦定值,有:
考虑第 \(i + 1\) 条边的值为左端点,那么第 \(i\) 条边的值显然不能为右端点,有:
考虑第 \(i + 1\) 条边的值为右端点,显然:
现在考虑 \(dp\) 对 \(f\) 的贡献,枚举头连到尾的边的贡献,分讨一下:
预处理 \(dp\) 和 \(f\) 可以做到 \(O(n ^ 2 + Tn ^ 2)\)。做完了。
code
#include <bits/stdc++.h>
using namespace std;
#define int ll
#define ll long long
#define usd unsigned
#define el putchar('\n')
#define lowbit(x) (x & (-x))
#define AC return
#define AK return 0
#define YS cout << "YES"
#define NO cout << "NO"
#define Ys cout << "Yes"
#define No cout << "No"
#define ys cout << "yes"
#define no cout << "no"
#define ls(i) ch[i][0]
#define rs(i) ch[i][1]
#define debug(num) cerr << #num << ' ' << num << '\n'
#define void inline void
#define il inline
#define Mod(x) (((x) % mod + mod) % mod)
#define pii pair <int, int>
#define pll pair <ll, ll>
il char gc() { char c; while((c = getchar()) <= ' ') ; return c; }
il int rd() {
char c; int x, f = 1;
while(!isdigit(c = getchar())) if(c == '-') f *= -1;
x = c ^ 48;
while(isdigit(c = getchar())) x = (x << 3) + (x << 1) + (c ^ 48);
return x * f;
}
void ACehomoxue();
signed main() {
srand(time(0));
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
int t = 1;
t = rd();
while(t--) ACehomoxue();
AK;
}
const int mod = 1e9 + 7, maxn = 1000 + 18;
int n, a[maxn], b[maxn], dp[maxn][maxn][2][2], f[maxn][maxn], g[maxn], tmp[maxn], ans = 0;
void add(int &x, int y) { x = (x + y) % mod; }
int fact[maxn], inv[maxn];
il int ksm(int a, int k = mod - 2) { int res = 1; for(; k; k >>= 1, a = a * a % mod) if(k & 1) res = res * a % mod; return res; }
il int binom(int n, int m) { if(n < 0 || m < 0 || n - m < 0) AK; return fact[n] * inv[m] % mod * inv[n - m] % mod; }
void pre() {
if(inv[0]) AC;
fact[0] = 1;
for(int i = 1; i < maxn; i++) fact[i] = fact[i - 1] * i % mod;
inv[maxn - 1] = ksm(fact[maxn - 1]);
for(int i = maxn - 2; i + 1; i--) inv[i] = inv[i + 1] * (i + 1) % mod;
int N = 1000;
dp[1][0][1][0] = 1;
dp[1][1][0][0] = 1;
dp[1][1][1][1] = 1;
for(int i = 1; i <= N; i++) {
for(int j = 0; j <= i; j++) {
for(int t = 0; t <= 1; t++) {
add(dp[i + 1][j][t][0], dp[i][j][t][0] + dp[i][j][t][1]);
add(dp[i + 1][j + 1][t][1], dp[i][j][t][0] + dp[i][j][t][1]);
add(dp[i + 1][j + 1][t][0], dp[i][j][t][0]);
}
}
}
for(int i = 1; i <= N; i++) {
for(int j = 0; j <= i; j++) {
for(int s : {0, 1}) {
for(int t : {0, 1}) {
add(f[i + 1][j], dp[i][j][s][t]);
if(s == 1) add(f[i + 1][j + 1], dp[i][j][s][t]);
if(t == 0) add(f[i + 1][j + 1], dp[i][j][s][t]);
}
}
}
}
}
vector <int> vec[maxn];
bool vis[maxn];
il int dfs(int x) {
int sz = 1;
vis[x] = true;
for(int to : vec[x]) {
if(vis[to]) continue;
sz += dfs(to);
}
return sz;
}
void ACehomoxue() {
pre();
memset(g, 0, sizeof(g));
memset(vis, 0, sizeof(vis));
n = rd(), g[0] = 1, ans = 0;
for(int i = 1; i <= n; i++) a[i] = rd();
for(int i = 1; i <= n; i++) b[i] = rd();
for(int i = 1; i <= n; i++) {
vec[a[i]].push_back(b[i]);
vec[b[i]].push_back(a[i]);
}
for(int i = 1, m; i <= n; i++) {
if(vis[i]) continue;
m = dfs(i);
for(int i = 0; i <= n; i++) tmp[i] = g[i], g[i] = 0;
for(int i = 0; i <= n; i++) {
for(int j = 0; j <= m; j++) {
g[i + j] = (g[i + j] + tmp[i] * f[m][j]) % mod;
}
}
}
for(int i = 0, v; i <= n; i++) {
v = g[i] % mod * fact[n - i] % mod;
if(i & 1) ans = (ans + mod - v) % mod;
else ans = (ans + v) % mod;
vec[i].clear();
}
cout << ans;
el;
}
gourds
水黑。
我们考虑先求出 \(C\) 吃的方案数,然后再求出 \(c\) 的方案数。假设 \(c\) 吃的 \(\frac n 3\) 的方案数为 \(f\),设三个人吃的序列为 \(aa\) 和 \(bb\) 和 \(cc\),那么对于 \((aa_i, bb_i, cc_i)\),注意到 \(cc_i\) 在 \(c\) 中一定排在最前面,概率为 \(\frac 1 3\),在 \(c\) 中 \(n\) 个位置,选 \(\frac n 3\) 个位置出来放 \(cc\) 并且满足上面的限制,显然有答案:
现在考虑求 \(f\)。我们考虑一个一个匹配 \((aa_i, bb_i, cc_i)\),考虑 \(dp_{i, j, k, t}\) 表示 \(a\) 匹配到了第 \(i\) 个数 \(b\) 匹配到了第 \(j\) 个,还剩了 \(k\) 个 \((aa_i, bb_i, cc_i)\) 中的 \(cc_i\) 没有匹配,我们钦定先用 \(a\) 中的数填前面缺的 \(cc_i\) 再用 \(b\) 中的数,\(t\) 表示现在是在用 \(a\) 中的还是 \(b\) 中的(当 \(t = 1\) 时是用 \(a\)),不难发现此时 \(f = dp_{n + 1, n + 1, 0, 0} + dp_{n + 1, n + 1, 0, 1}\),且有初始状态 \(dp_{1, 1, 0, 1} = 1\)。
考虑转移,我们设 \(pa_i\) 表示 \(i\) 在 \(a\) 中出现的位置,\(pb_i\) 同理,当 \(dp_{i, j, k, t}\) 有 \(j > pb_{a_i}\) 说明 \(a_i\) 已经被匹配过了,那么 \(dp_{i + 1, j, k, t} \gets dp_{i + 1, j, k, t} + dp_{i, j, k, t}\) 即可,当 \(i > pa_{b_j}\) 时同理。
考虑当 \(a_i = b_j\),那么 \(a_i\) 这个数一定要拿去前面填缺的 \(cc_i\) 否则不合法,为了去重,考虑钦定这样子是在拿 \(a_i\) 去填,那么有转移:
对于其他 \(a_i \ne b_j\),那么其可以作为一组 \((aa_i, bb_i, cc_i)\) 中的 \((aa_i, bb_i)\),\(cc_i\) 暂时先空着,显然有:
分类 \(a_i\) 或 \(b_j\) 拿去填缺的 \(k\) 个 \(cc_i\),分别有:
直接做即可,时间复杂度 \(O(n ^ 3)\),注意空间是开不下的,要按照 \(i + j\) 滚动数组一下。做完了。
graph
太难了以后有缘再改。
2026.10.7
估:100 + 0 + 35
实际:100 + 0 + [0, 35]
t3 原题没有数据分层。
t3 如果会斯特林数就是唐题,但我不会,已严肃恶补。
froggay
不敢交到洛谷题解区。
直接记忆化爆搜即可,每次到一个位置,线段树暴力二分转弯位置,暴力去做。实测 \(400ms\)。
code
#include <bits/stdc++.h>
using namespace std;
#define int int
#define ll long long
#define usd unsigned
#define el putchar('\n')
#define lowbit(x) (x & (-x))
#define AC return
#define AK return 0
#define YS cout << "YES"
#define NO cout << "NO"
#define Ys cout << "Yes"
#define No cout << "No"
#define ys cout << "yes"
#define no cout << "no"
#define ls(i) ch[i][0]
#define rs(i) ch[i][1]
#define debug(num) cerr << #num << ' ' << num << '\n'
#define void inline void
#define il inline
#define Mod(x) (((x) % mod + mod) % mod)
#define pii pair <int, int>
#define pll pair <ll, ll>
il char gc() { char c; while((c = getchar()) <= ' ') ; return c; }
il int rd() {
char c; int x, f = 1;
while(!isdigit(c = getchar())) if(c == '-') f *= -1;
x = c ^ 48;
while(isdigit(c = getchar())) x = (x << 3) + (x << 1) + (c ^ 48);
return x * f;
}
void ACehomoxue();
signed main() {
srand(time(0));
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
int t = 1;
// t = rd();
while(t--) ACehomoxue();
AK;
}
const int mod = 998244353, maxn = 5 * 1e4 + 18;
int n, m, q, a[maxn], b[maxn];
class xds {
struct tree {
int l, r, mx;
} t[2][maxn << 2];
void push_up(int d, int i) { t[d][i].mx = max(t[d][i << 1].mx, t[d][i << 1 | 1].mx); }
public:
void build(int d, int i, int l, int r) {
t[d][i].l = l, t[d][i].r = r;
if(l == r) {
t[d][i].mx = d ? b[l] : a[l];
AC;
}
int mid = (l + r) >> 1;
build(d, i << 1, l, mid);
build(d, i << 1 | 1, mid + 1, r);
push_up(d, i);
}
il int query(int d, int i, int l, int r, int k, int opt) {
if(l > r || t[d][i].l > r || t[d][i].r < l || t[d][i].mx <= k) return -1;
if(t[d][i].l == t[d][i].r) return t[d][i].l;
int res = -1;
if(!opt) {
res = query(d, i << 1 | 1, l, r, k, opt);
if(res != -1) return res;
return query(d, i << 1, l, r, k, opt);
}
res = query(d, i << 1, l, r, k, opt);
if(res != -1) return res;
return query(d, i << 1 | 1, l, r, k, opt);
}
} ds;
unordered_map <ll, ll> dp;
il ll get(int d, int t, int x, int y) {
return ((1ll * (x - 1) * m + y - 1) << 2) | (d << 1) | t;
}
il ll dfs(int d, int t, int x, int y) {
ll id = get(d, t, x, y);
if(dp.count(id)) return dp[id];
int j = d ? x : y, len = d ? n : m, k = d ? b[y] : a[x];
int nxt = t ? ds.query(d ^ 1, 1, j + 1, len, k, t) : ds.query(d ^ 1, 1, 1, j - 1, k, t);
if(nxt == -1) return dp[id] = t ? len - j : j - 1;
int nx = d ? nxt : x, ny = d ? y : nxt;
dp[id] = abs(nxt - j) + max(dfs(d ^ 1, 0, nx, ny), dfs(d ^ 1, 1, nx, ny));
return dp[id];
}
void ACehomoxue() {
n = rd(), m = rd(), q = rd();
for(int i = 1; i <= n; i++) a[i] = rd();
for(int i = 1; i <= m; i++) b[i] = rd();
ds.build(0, 1, 1, n);
ds.build(1, 1, 1, m);
for(; q--; ) {
int x = rd(), y = rd();
ll ans = max(max(dfs(0, 0, x, y), dfs(0, 1, x, y)), max(dfs(1, 0, x, y), dfs(1, 1, x, y)));
cout << ans;
el;
}
}
shaber
太几把难了有缘再补。
glaph
严肃学习斯特林数。
对于原来的 \(\sum_{S} f(S) ^ k\),用斯特林数拆开得到:
提取公因式:
我们设 \(f_i\) 任意 \(S\) 形成的生成树中选了 \(i\) 条边标记的方案数,注意到组合意义 \(f_i = \sum_{S} \binom {f(S)} {i}\),那么答案就为:
考虑求 \(f_i\)。设 \(dp_{x, j}\) 表示 \(lca(S)\) 在 \(x\) 的子树选了 \(j\) 条标记边的方案数,设 \(g_{x, j}\) 表示算上 \(x\) 到父亲的边标记边有 \(i\) 条的方案数。那么初始状态有 \(dp_{x, 0} = 2\) 表示第 \(x\) 个点是否在集合内,对于转移,枚举儿子 \(to\),不难发现转移是一个子树卷积(书上背包),有:
然后算对于所有 \(f_i\) 的贡献,算选了 \(i\) 个且 \(lca(S)\) 就是 \(x\) 的方案数,有:
考虑求 \(g\),不难发现分类 \((x, fa)\) 是否标记有:
注意最后 \(g_{x, 1}\) 要减去 \(1\),因为有 \(x\) 子树内没有点但是 \((x, fa)\) 却被标记了的不合法方案。把 dp 写成子树卷积形式似乎可以做到 \(O(nk + k ^ 2)\) 但我不会证明。做完了。

浙公网安备 33010602011771号