国庆自主考试总结

分了两天,是两次省选考试。

2026.10.6

估:100 + 100 + 0

实:100 + 100 + 0

为什么都会 t3 的 30 pts。

perm

link

考虑容斥,设 \(g_i\) 表示 \(C\) 确定有 \(i\) 个位置不满足条件其他位置不管的方案数,那么显然有答案:

\[\sum_{i = 0} (-1) ^ i g_i (n - 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\) 条边不钦定值,有:

\[dp_{i + 1, j, t, 0} \gets dp_{i + 1, j, t, 0} + (dp_{i, j, t, 0} + dp_{i, j, t, 1}) \]

考虑第 \(i + 1\) 条边的值为左端点,那么第 \(i\) 条边的值显然不能为右端点,有:

\[dp_{i + 1, j + 1, t, 0} \gets dp_{i + 1, j + 1, t, 0} + dp_{i, j, t, 0} \]

考虑第 \(i + 1\) 条边的值为右端点,显然:

\[dp_{i + 1, j + 1, t, 0} \gets dp_{i + 1, j + 1, t, 0} + (dp_{i, j, t, 0} + dp_{i, j, t, 1}) \]

现在考虑 \(dp\) 对 \(f\) 的贡献,枚举头连到尾的边的贡献,分讨一下:

\[f_{i + 1, j} \gets f_{i + 1, j} + dp_{i, j, 0/1, 0/1} \quad f_{i + 1, j + 1} \gets f_{i + 1, j + 1} + dp_{i, j, 1, 0/1} + dp_{i, j, 0/1, 0} \]

预处理 \(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

link

水黑。

我们考虑先求出 \(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\) 并且满足上面的限制,显然有答案:

\[\boxed{\frac {\binom{n}{\frac n 3} (n - \frac n 3)!} {{3} ^ \frac n 3} \times f} \]

现在考虑求 \(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\) 去填,那么有转移:

\[dp_{i + 1, j, k - 1, 1} \gets dp_{i + 1, j, k - 1, 1} + k \times dp_{i, j, k, 1} \]

对于其他 \(a_i \ne b_j\),那么其可以作为一组 \((aa_i, bb_i, cc_i)\) 中的 \((aa_i, bb_i)\),\(cc_i\) 暂时先空着,显然有:

\[dp_{i + 1, j + 1, k + 1, 1} \gets dp_{i + 1, j + 1, k + 1, 1} + dp_{i, j, k, 0/1} \]

分类 \(a_i\) 或 \(b_j\) 拿去填缺的 \(k\) 个 \(cc_i\),分别有:

\[dp_{i + 1, j, k - 1, 1} \gets dp_{i + 1, j, k - 1, 1} + k \times dp_{i, j, k, 1} \quad \\ dp_{i, j + 1, k - 1, 0} \gets dp_{i, j + 1, k - 1, 0} + k \times dp_{i, j, k, 0/1} \]

直接做即可,时间复杂度 \(O(n ^ 3)\),注意空间是开不下的,要按照 \(i + j\) 滚动数组一下。做完了。

code

graph

link

太难了以后有缘再改。

2026.10.7

估:100 + 0 + 35

实际:100 + 0 + [0, 35]

t3 原题没有数据分层。

t3 如果会斯特林数就是唐题,但我不会,已严肃恶补。

froggay

link

不敢交到洛谷题解区。

直接记忆化爆搜即可,每次到一个位置,线段树暴力二分转弯位置,暴力去做。实测 \(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

link

严肃学习斯特林数。

对于原来的 \(\sum_{S} f(S) ^ k\),用斯特林数拆开得到:

\[\sum_{S} \sum_{i = 1} ^ k \left\{ \begin{matrix} k \\ i \end{matrix} \right\}i! \binom {f(S)} {i} \]

提取公因式:

\[\sum_{i = 1} ^ k \left\{ \begin{matrix} k \\ i \end{matrix} \right\}i! \sum_{S} \binom {f(S)} {i} \]

我们设 \(f_i\) 任意 \(S\) 形成的生成树中选了 \(i\) 条边标记的方案数,注意到组合意义 \(f_i = \sum_{S} \binom {f(S)} {i}\),那么答案就为:

\[\boxed{\sum_{i = 1} ^ k \left\{ \begin{matrix} k \\ i \end{matrix} \right\}i! f_i} \]

考虑求 \(f_i\)。设 \(dp_{x, j}\) 表示 \(lca(S)\) 在 \(x\) 的子树选了 \(j\) 条标记边的方案数,设 \(g_{x, j}\) 表示算上 \(x\) 到父亲的边标记边有 \(i\) 条的方案数。那么初始状态有 \(dp_{x, 0} = 2\) 表示第 \(x\) 个点是否在集合内,对于转移,枚举儿子 \(to\),不难发现转移是一个子树卷积(书上背包),有:

\[dp_{x, a + b} \gets dp_{x, a + b} + dp_{x, a} g_{to, b} \]

然后算对于所有 \(f_i\) 的贡献,算选了 \(i\) 个且 \(lca(S)\) 就是 \(x\) 的方案数,有:

\[f_i \gets f_i + dp_{x, i} - \sum_{to} g_{to, i} \]

考虑求 \(g\),不难发现分类 \((x, fa)\) 是否标记有:

\[g_{x, i} = dp_{x, i - 1} + dp_{x, i} \]

注意最后 \(g_{x, 1}\) 要减去 \(1\),因为有 \(x\) 子树内没有点但是 \((x, fa)\) 却被标记了的不合法方案。把 dp 写成子树卷积形式似乎可以做到 \(O(nk + k ^ 2)\) 但我不会证明。做完了。

code

posted @ 2026-10-07 20:12  ACehomoxue  阅读(10)  评论(0)    收藏  举报