NOI2025福建省队集训部分题解

· · 个人记录

D1T1

题意

交互题,有一颗高度为 h 的满二叉树,有 n 个点,每个点有一个点权 f_i,你不知道 f 序列。现在你需要得到 \sum_{i=1}^n f_i。保证 f 是正整数。

你可以通过询问与一个点距离为 d 的所有点的点权值和,其中 d > 0,如果没有距离为 d 的点,返回 0。注意,这里你不知道每个点在树上的位置,因为每个点已经重新编号了。

要求在 2n+3 次询问内得到答案,且单个点的询问次数不超过 4。交互库自适应。

分析

首先容易想到我们先对每个点都询问以下距离为 1 的值,全部加起来,设这个值为 S,那么我们发现,S 包括了 3 倍的中间结点权值和,2 倍的根节点权值,1 倍的叶子节点权值和。如果我们可以让所有权值都是 3 倍的话,我们就可以直接除 3 解决了。

首先想到求根节点是哪个。不难想到可以直接对每个点查询距离为 h 的。因为根节点距离最远的点是 h - 1 的,所以只有根节点是 0

那么现在可以直接对根节点查询距离为 h - 1 的权值和,这个值就是叶子节点权值和。此时我们只剩下求根节点权值了。

不难想到对根节点两个儿子查询距离为 1,加起来,此时发现这个值包括了 2 倍的根节点权值和第 3 层的权值和。我们要减去第 3 层的权值和,所以对根节点查询距离为 2

而至于找到根节点的两个儿子,可以查询距离 h + 1 的所有点,如果是 0 且不是根节点,那么就是这两个儿子。

现在我们就有一个可以 O(n) 求出答案的方法,计算一下发现用了 3n+4 次。精细实现一下。

首先在求根节点两个儿子用了 n 次,但实际上我们可以换一下顺序,就是先求出所有 h+1 的值是 0 的点,容易知道只有 3 个。然后对于这 3 个点再查询 h 求出根节点,这样子就只用了 n+3 次。

当然这个还可以再优化,因为我们知道一定有恰好 3 个点距离 h+1 值为 0。这样子我们可以只用查询前 n - 1 个点,如果只有 2 个点,说明最后一个一定也是距离 h+1 值为 0 的点。

虽然说在 3 个点中找根节点也可以这样优化,但是我们后面还要用到两个儿子距离为 h 的权值和,所以先不优化。

现在我们用了 2n+6 次。但是注意到查询两个儿子距离为 1 的权值和在之前已经查询过了,所以我们可以存下来,此时变成 2n+4

最后我们来优化求叶子节点,我们不用从根节点查询距离 h-1,我们可以变成查询两个儿子距离为 h 的和。因为我们前面在找根节点时已经查询过了,所以不用再查询,此时只用了 2n+3 次,且询问次数最多的根节点恰好询问了 4 次。

#include "tree.h"
#include <bits/stdc++.h>
using  namespace std;
#define ll long long
ll res[1000005], point[4], val[3];
ll solve(int subtask, int h){
    int n = (1 << h) - 1;
    ll ans = 0, sum = 0, cnt = 0, root = 0, prt = 0;
    if (h == 2){
        for (int i = 1; i <= n; i++) for (int j = 1; j <= h + h - 2; j++) ans += ask(i, j);
        return ans / (n - 1); 
    }
    for (int i = 1; i <= n; i++) sum += (res[i] = ask(i, 1));
    for (int i = 1; i < n; i++) if (!ask(i, h + 1)) point[++cnt] = i;
    if (cnt == 2) point[++cnt] = n; 
    for (int i = 1; i <= 3; i++){
        val[i] = ask(point[i], h);
        if (!val[i]) root = point[i];
    }
    for (int i = 1; i <= 3; i++) if (point[i] != root) prt += res[point[i]];
    prt -= ask(root, 2), prt /= 2;
    ans = sum + prt + (val[1] + val[2] + val[3]) * 2;
    return ans / 3;
}

D2T1

题意

给定一个序列 a,你可以进行若干次操作,每次操作可以选择一个区间,将每个数依次进行 +1,-1,0,+1,-1,0,\cdots 的操作,问至少几次操作后,可以让 a 中所有元素都变成 0。无解输出 -1

分析

首先考虑无解情况,注意到一次操作后前缀和只会变大,所以我们计算所有前缀和,判断是否存在有大于 0 的即可。存在输出 -1

考虑证明剩余情况一定有解。不难想到我们可以简化成每次只用 +1,-1+1 这两种操作。所以一定有解。

接下来考虑求最优解。因为一次操作是对于区间的,不好做,我们转化一下。设 b_i = a_{i-2}+a_{i-1}+a_i。此时我们就有 3 种情况。

  1. 结尾的操作是 +1,-1,此时就是 b_l + 1,b_{r+2}-1
  2. 结尾操作是 +1,-1,0,此时就是 b_l + 1,b_{r+1}-1
  3. 结尾操作是 +1,此时就是 b_l + 1,b_{r+1}+1,b_{r+2}+1

我们发现只有 3 操作会对总和产生影响,而总和是固定的,所以我们可以先求出总和,此时就可以计算出 3 操作的总次数。

而对于 1,2 操作,不难发现是等价的。我们可以归为一类。

现在我们从后往前考虑计算。分类讨论。

按照这个步骤就可以直接 O(n) 计算了。不要忘记开 long long

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define il inline
#define N 100005
il int rd(){
    int s = 0, w = 1;
    char ch = getchar();
    for (;ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1;
    for (;ch >= '0' && ch <= '9'; ch = getchar()) s = ((s << 1) + (s << 3) + ch - '0');
    return s * w;
}
int n, T, a[N], b[N], Left[3], Right;
void Main(){
    Left[0] = Left[1] = Left[2] = Right = 0;
    for (int i = 1; i <= n; i++) a[i] = rd();
    n += 2;
    int sum = 0, ans = 0;
    for (int i = 1; i <= n; i++){
        sum += a[i];
        if (sum > 0) return puts("-1"), void(0);
    }
    for (int i = 1; i <= n; i++) b[i] = (i == 1 ? 0 : a[i - 2]) + a[i - 1] + a[i], Right -= b[i];
    Right /= 3;
    for (int i = n; i >= 1; i--){
        if (b[i] >= 0) Left[i % 3] += b[i], ans += b[i];
        if (b[i - 1] < 0 && b[i] < 0 && Right){// 还可以用 3 操作,两个数都小于 0,那么一起加 
            int tmp = min(-max(b[i - 1], b[i]), Right);// 操作次数 
            Right -= tmp, b[i - 1] += tmp, b[i] += tmp, Left[(i + 1) % 3] += tmp, ans += tmp;
        }
        if ((b[i - 1] >= 0 && b[i] < 0) || (b[i - 1] < 0 && b[i] < 0 && !Right)){// 如果还小于 0,或者前面大于 0,用之前剩下的左端点 
            if (b[i] + Left[i % 3] >= 0) Left[i % 3] += b[i], b[i] = 0;
            else b[i] += Left[i % 3], Left[i % 3] = 0;
        }
        if (b[i] < 0){// 如果还小于 0,用右端点 
            int tmp = min(-b[i], Right);// 操作次数 
            Right -= tmp, b[i - 1] += tmp, b[i] += tmp, Left[(i + 1) % 3] += tmp, ans += tmp;
        }
    }
//  cout << Left[0] << " " << Left[1] << " " << Left[2] << "\n";
    printf ("%lld\n", ans);
}
signed main(){
    freopen ("trans.in", "r", stdin);
    freopen ("trans.out", "w", stdout);
    T = rd(), n = rd();
    for (; T--;) Main(), n -= 2;
    return 0;
}

D3T1

题意

有一个长度是 n + m 的序列,有 c + 1 种颜色,共有 m 个,其中第 c + 1 颜色是特殊颜色。给定义一个长度为 n + 1 的序列 w,以及对于每个颜色 1 \sim c 出现次数 a_ia_i 个数 v_{i,j}。我们定义一个序列的权值如下。

求对于所有可能序列的权值之和。

## 分析 首先考虑一个暴力,枚举 $x,u,y$。注意到因为 $\sum a_i = n$,所以这样子时间复杂度是 $O(n^2)$,考虑公式。下面 $n$ 表示序列总长度,即 $n+m$。 这种情况的权值是 $w_x \times v_{u,y}$,然后先计算出所有普通颜色的排列方案数,这个是简单的,$O(n)$ 预处理即可。设这个值是 $mul$。那么我们可以容易的计算出除了颜色 $u$ 以外剩下点任意排的方案数,就是 $\frac{mul}{C_{n-m,a_u}}$,设为 $t_u$。 现在考虑计算方案数。先将所有特殊颜色放好。这个方案数是 $\binom{n-x-y}{m-1}$,然后考虑颜色 $u$ 的放置方案。但是考虑到特殊颜色是否截断了 $u$ 会影响方案数,所以分类讨论。 1. 不是特殊颜色截断了颜色 $u$,方案数是 $\binom{n-x-y-1}{m-1}\binom{n-m-y-1}{a_u-y}t_u$。 2. 是特殊颜色阶段了颜色 $u$,方案数是 $\binom{n-x-y-1}{m-2}\binom{n-m-y}{a_u-y}t_u$。 那么我们直接分开计算。 我们似乎漏掉了结尾是特殊颜色的情况,但是这个是好算的,就是 $\sum_{x=1}^{n-m+1} w_x\binom{n-x-1}{m-1}mul$。这个 $O(n)$ 计算,后文的优化不在考虑。 现在我们就有了 $O(n^2)$ 的算法,考虑优化。从部分分入手,有一个是 $w_i = 1$,此时不用考虑 $w_x$ 的贡献。此时发现我们不用枚举 $x$,可以直接枚举 $x+y$。此时发现可以提出来一个与 $y$ 无关的式子,剩下部分可以直接预处理计算,时间复杂度 $O(n)$。 实际上,我们可以枚举 $x+y$,此时发现提出来与 $y$ 无关的部分,剩下的就是**加法卷积**,可以直接 NTT 即可。时间复杂度 $O(n\log n)$。 ps:出题人做法是 $O(nm)$ 的 dp,有好写很多,但是依赖于 $m \le 30$。 ```cpp #include <bits/stdc++.h> using namespace std; #define poly vector <int> #define il inline il int rd(){ int s = 0, w = 1; char ch = getchar(); for (;ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1; for (;ch >= '0' && ch <= '9'; ch = getchar()) s = ((s << 1) + (s << 3) + ch - '0'); return s * w; } const int N = (1 << 20) + 5; namespace Poly{ int rev[N]; const int P = 998244353, G = 3, invG = 332748118; il int ksm(int x, int r){ int ans = 1; for (; r; x = 1ll * x * x % P, r >>= 1) if (r & 1) ans = 1ll * ans * x % P; return ans; } il poly get(int n){return poly(n + 1);} il void NTT (poly &a, int n, int typ){ for (int i = 0; i < n; i++) if (i < rev[i]) swap(a[i], a[rev[i]]); for (int len = 1; len < n; len <<= 1){ int wn = ksm(typ == 1 ? G : invG, (P - 1) / (len << 1)); for (int i = 0; i < n; i += (len << 1)){ int w = 1; for (int j = 0; j < len; j++){ int x = a[i + j], y = 1ll * w * a[i + j + len] % P; a[i + j] = (x + y) % P, a[i + j + len] = (x - y + P) % P, w = 1ll * w * wn % P; } } } if (typ == -1){ int inv = ksm(n, P - 2); for (int i = 0; i < n; i++) a[i] = 1ll * a[i] * inv % P; } } il poly mod(poly a, int n){a.resize(n, 0);return a;} il poly operator *(poly a, poly b){ int n = a.size() - 1, m = b.size() - 1, L = 1; while (L <= n + m) L <<= 1; a.resize(L), b.resize(L); for (int i = 0; i < L; i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) ? (L >> 1) : 0); NTT(a, L, 1), NTT(b, L, 1); int inv = ksm(L, P - 2); for (int i = 0; i < L; i++) a[i] = 1ll * a[i] * b[i] % P; NTT(a, L, -1); return mod(a, n + m + 1); } }; using namespace Poly; int n, m, Ct, a[N], ans, fact[N], inft[N], mul = 1, t[N]; vector <int> v[N], pos[N]; poly f, w, g; il void init(int n){ fact[0] = 1; for (int i = 1; i <= n; i++) fact[i] = 1ll * fact[i - 1] * i % P;inft[n] = ksm(fact[n], P - 2); for (int i = n; i >= 1; i--) inft[i - 1] = 1ll * inft[i] * i % P; } il int C(int n, int m){return n == m ? 1 : m < 0 || n < m ? 0 : 1ll * fact[n] * inft[m] % P * inft[n - m] % P;} signed main(){ n = rd(), m = rd(), Ct = rd(), n += m, f = w = get(n), init(n); for (int i = 1; i <= n - m + 1; i++) w[i] = rd(); for (int i = 1; i <= Ct; i++) a[i] = rd(); for (int i = 1; i <= Ct; i++){ v[i].resize(a[i] + 1); for (int j = 1; j <= a[i]; j++) v[i][j] = rd(), pos[j].push_back(i); } for (int i = 1, sum = 0; i <= Ct; i++) mul = 1ll * mul * C(n - m - sum, a[i]) % P, sum += a[i]; for (int i = 1; i <= Ct; i++) t[i] = 1ll * mul * ksm(C(n - m, a[i]), P - 2) % P; for (int j = 1; j <= n; j++) for (int c : pos[j]) f[j] = (f[j] + 1ll * C(n - m - j - 1, a[c] - j) * v[c][j] % P * t[c] % P) % P; g = f * w; for (int i = 1; i <= n; i++) ans = (ans + 1ll * C(n - i - 1, m - 1) * g[i] % P) % P; f = get(n); for (int j = 1; j <= n; j++) for (int c : pos[j]) f[j] = (f[j] + 1ll * C(n - m - j, a[c] - j) * v[c][j] % P * t[c] % P) % P; g = f * w; for (int i = 1; i <= n; i++) ans = (ans + 1ll * C(n - i - 1, m - 2) * g[i] % P) % P; for (int i = 1; i <= n - m + 1; i++) ans = (ans + 1ll * w[i] * C(n - i - 1, m - 2) % P * mul % P) % P; printf ("%d\n", ans); return 0; } ``` # D5T1 ## 题意 有 $n$ 个英雄,第 $i$ 个战力是 $a_i$,以及 $n$ 个怪兽,防御力是 $b_i$。现在你可以任意安排每个英雄打哪个怪兽,但是每个英雄要恰好打一个。当一个英雄的战力大于一个怪兽的防御力时,这个英雄就会获胜。保证所有 $a$ 和 $b$ 构成一个长度为 $2n$ 的排列。 有 $q$ 次询问,每次给定区间 $[l,r]$,问有多少个集合 $S$ 满足 $|S| \in [l,r]$,且存在一种安排英雄的方法,可以使得 $S$ 中所有英雄可以获胜,其余英雄无法获胜。模 $998244353$。 $1 \le n \le 5000$。 ## 分析 ps:绷不住了,考了 D1 下午讲题的第一题的[原题](https://www.luogu.com.cn/problem/P12558)。 首先不难想到我们需要求出有 $i$ 个英雄获胜的方案数即可。先考虑如何判断一个安排方式是否可行。 我们假设希望 $S$ 中的英雄获胜,其余英雄无法获胜,那么可以将 $b$ 排序,然后将 $|S|$ 小的几个分配给 $S$ 中的英雄,其余的分配给剩余英雄。而且我们一定将 $S$ 中 $a$ 最小的分配 $b$ 最小的,$S$ 第二小的分配 $b$ 第二小的,以此类推。于是我们将 $a,b$ 排序,然后考虑一个 dp。 设 $f_{i,j}$ 表示前 $i$ 的英雄打败了 $j$ 个怪物的方案数。考虑第 $i$ 个英雄,他有两种情况,分别是获胜和没有获胜。如果获胜是简单的,考虑直接将 $a_i$ 和 $b_j$ 比较,如果 $a_i > b_j$,说明 $i$ 可以获胜。接下来考虑没有获胜。这就会出现一个问题,就是我们不知道最终我们需要几个英雄获胜,我们就不知道 $i$ 和谁配对。此时我们无法避免的要枚举最外层一个 $k$ 表示我们需要有 $k$ 个英雄获胜。这样子时间复杂度就是 $O(n^3)$ 的。 现在我们考虑优化。枚举的 $k$ 是无法避免的,但是我们发现在 $i$ 获胜时是与 $k$ 无关的。此时我们就有一个想法,就是拆开两部分来算。我们要利用一个点,就是对于 $a_i < b_k$ 的点,一定可以不在 $S$ 中,而 $a_i > b_k$ 的点,一定可以在 $S$ 中。此时我们如果分别 dp,前者就不用考虑不在 $S$ 的情况,后者不用考虑在 $S$ 的情况。 设 $f_{i,j}$ 表示在 $a_{1\sim i}$ 中有 $j$ 个英雄获胜的方案数,此时我们有一个默认条件,就是 $a_i < b_k$。这个条件是我们等会统计答案时人为规定的。 同理的,设 $g_{i,j}$ 表示在 $a_{n-i+1 \sim n}$ 中有 $j$ 个英雄没有获胜的方案数,默认条件就是 $a_{n-i+1} > b_k$。 这两个 dp 都可以容易的 $O(n^2)$ 求解。此时我们就要统计答案。枚举 $k$,然后找到最后一个满足 $a_i < b_k$ 的位置 $p$。枚举在前 $i$ 个有几个人获胜,设为 $x$,那么在后 $n-i$ 个人中就有 $n-k-(x-p)$ 个人没有获胜。这个贡献就是 $f_{p,x} \times g_{n-p,n-k-(x-p)}$。 统计完之后直接前缀和预处理,然后查询即可。时间复杂度 $O(n^2+q)$。 ```cpp #include <bits/stdc++.h> using namespace std; #define ll long long #define il inline #define N 5005 il int rd(){ int s = 0, w = 1; char ch = getchar(); for (;ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1; for (;ch >= '0' && ch <= '9'; ch = getchar()) s = ((s << 1) + (s << 3) + ch - '0'); return s * w; } const int P = 998244353; int n, a[N], b[N], f[N][N], g[N][N], ans[N]; signed main(){ n = rd(); for (int i = 1; i <= n; i++) a[i] = rd(); for (int i = 1; i <= n; i++) b[i] = rd(); sort (a + 1, a + n + 1), sort (b + 1, b + n + 1); f[0][0] = g[0][0] = 1; for (int i = 1; i <= n; i++) for (int j = 0; j <= i; j++){ f[i][j] = f[i - 1][j]; if (j && a[i] > b[j]) f[i][j] = (f[i][j] + f[i - 1][j - 1]) % P; } for (int i = 1; i <= n; i++) for (int j = 0; j <= i; j++){ g[i][j] = g[i - 1][j]; if (j && a[n - i + 1] < b[n - j + 1]) g[i][j] = (g[i][j] + g[i - 1][j - 1]) % P; } for (int i = 0; i <= n; i++){ int p = 0; for (; p + 1 <= n && a[p + 1] < b[i]; p++); for (int x = 0; x <= i; x++){ int y = n + x - i - p; if (y >= 0 && y <= n - p) ans[i] = (ans[i] + 1ll * f[p][x] * g[n - p][y] % P) % P; } } for (int i = 1; i <= n; i++) ans[i] = (ans[i - 1] + ans[i]) % P; for (int T = rd(), l, r, res; T--;){ l = rd(), r = rd(), res = ans[r]; if (l) res = (res - ans[l - 1] + P) % P; printf ("%d\n", res); } return 0; } ``` # D6T1 ## 题意 给定 $n,m$,随机生成一个范围是 $[1,m]$ 的**严格**单调递增数列 $a_i$,其差分数组是 $b_i$,特别的 $b_1 = a_1$。将 $b$ 排序,设其前缀和数组是 $c_i$,问对于每个 $i$ 的 $c_i$ 的期望值。模 $998244353$。 $1 \le n \le 5000, 1 \le m \le 10^6$。 ## 分析 ps:实际上可以用 NTT 做到 $O(n\log n + m\ln m)$ 的,但是由于 Tony2 不想超纲,所以放过了 $O(n^2 + m\ln m)$。 首先考虑到期望线性性,所以问题变成求 $b_k$ 的期望值。而我们不难想到这就是 k-max 的期望值,用 k-Min/Max 容斥。 $$E[b_k] = \sum_{T \subseteq S} (-1)^{|T|-k}\binom{|T|-1}{k-1}E[\min(T)]$$ 我们现在考虑求 $E[\min(T)]$。我们设 $f_j$ 表示 $|T|=j$ 的 $E[\min(T)]$ 值。我们考虑一个经典的 trick,将期望变成概率。 具体的,$E[\min(T)] = \sum_{i=1}^{\infty} P(\min(T) \ge i)$。 此时我们注意到 $P(\min(T) \ge i)$ 是简单的,就是 $\frac{\binom{m-|T|(i-1)}{n}}{\binom{m}{n}}$。具体原因考虑先给每个位置放置 $i$ 个数,然后剩下的数再分配。 然后我们就有可以结合之前的 k-Min/Max 容斥的式子得到下面的式子。 $$E[b_i] = \sum_{j=i}^n (-1)^{j-i}\binom{j-1}{i-1}\binom{n}{j}f_j$$ 这个式子可以 $O(n^2)$ 暴力求。实际上不难发现可以用 NTT 优化到 $O(n\log n)$,但是 Tony2 说超纲了。 尴尬的是我们求的是 k-max,但是题目要输出的是 k-min。所以你可以看到我的代码输出是从 $n$ 到 $1$ 的。 ```cpp #include <bits/stdc++.h> using namespace std; #define ll long long #define il inline #define N 1000005 il int rd(){ int s = 0, w = 1; char ch = getchar(); for (;ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1; for (;ch >= '0' && ch <= '9'; ch = getchar()) s = ((s << 1) + (s << 3) + ch - '0'); return s * w; } namespace Mint{ const int P = 998244353; class mint {public: int num; mint() = default; mint(int _num) : num((_num % P + P) % P) {} mint &operator = (int b) {return *this = mint(b);} friend bool operator <(mint a, mint b){return a.num < b.num;} friend bool operator >(mint a, mint b){return a.num > b.num;} friend bool operator <=(mint a, mint b){return a.num <= b.num;} friend bool operator >=(mint a, mint b){return a.num >= b.num;} friend bool operator ==(mint a, mint b){return a.num == b.num;} friend mint operator +(mint a, mint b){return mint((a.num + b.num) % P);} friend mint &operator +=(mint &a, mint b){return a = a + b;} friend mint operator +(mint a, int b){return a + mint(b);} friend mint &operator +=(mint &a, int b){return a = a + b;} friend mint &operator ++(mint &a){return a += 1;} friend mint operator ++(mint &a,int){mint copy(a);a += 1;return copy;} friend mint operator -(mint a, mint b){return mint(((a.num - b.num) % P + P) % P);} friend mint &operator -=(mint &a, mint b){return a = a - b;} friend mint operator -(mint a, int b){return a - mint(b);} friend mint &operator -=(mint &a, int b){return a = a - b;} friend mint &operator --(mint &a){return a -= 1;} friend mint operator --(mint &a,int){mint copy(a);a -= 1;return copy;} friend mint operator *(mint a, mint b){return mint((ll)a.num * b.num % P);} friend mint &operator *=(mint &a, mint b){return a = a * b;} friend mint operator *(mint a, int b){return a * mint(b);} friend mint &operator *=(mint &a, int b){return a = a * b;} mint inv(){ll ans = 1, x = num, r = P - 2;for (; r; x = x * x % P, r >>= 1) if (r & 1) ans = ans * x % P;return ans;} friend mint operator /(mint a, mint b){return a * b.inv();} friend mint &operator /=(mint &a, mint b){return a = a / b;} friend mint operator /(mint a, int b){return a / mint(b);} friend mint &operator /=(mint &a, int b){return a = a / b;} }; mint ksm(mint x, ll r){mint ans = 1;for (; r; x = x * x, r >>= 1) if (r & 1) ans = ans * x;return ans;} } using namespace Mint; int n, m; mint fact[N], inft[N], Eb[N], f[N]; il void init(int n){ fact[0] = 1; for (int i = 1; i <= n; i++) fact[i] = fact[i - 1] * i;inft[n] = ksm(fact[n], P - 2); for (int i = n; i >= 1; i--) inft[i - 1] = inft[i] * i; } il mint C(int n, int m){return fact[n] * inft[m] * inft[n - m];} signed main(){ freopen ("sort.in", "r", stdin); freopen ("sort.out", "w", stdout); n = rd(), m = rd(), init(m); mint inv = ksm(C(m, n), P - 2); for (int i = 1; i <= n; i++) for (int j = 1; m - i * (j - 1) >= n; j++) f[i] += C(m - i * (j - 1), n) * inv; for (int i = 1; i <= n; i++) for (int j = i; j <= n; j++) Eb[i] += (((j - i) & 1) ? P - 1 : 1) * C(n, j) * C(j - 1, i - 1) * f[j]; for (int i = n; i >= 1; i--) Eb[i] += Eb[i + 1], printf ("%d\n", Eb[i]); return 0; } ``` # D7T1 ## 题意 给定一个长度为 $n$ 的排列 $A$,问有多少个长度为 $n$ 个排列 $B$,满足可以通过 $A$ 进行若干次下述操作得到。模 $10^9+7$。 一次操作可以交换两个数,要求交换之后排列的逆序对个数减少。 $1 \le n \le 20$。 ## 分析 首先是经典的结论,就是一次操作一定使用前面小的数和后面大的数交换。 接下来考虑一个判定方法。对于 $\forall k\in[0,n-1]$,我们令 $\le k$ 的数是 $0$,其余的数是 $1$。设在 $k$ 的情况下的得到的 $A$ 序列是 $A_k$,$B$ 同理。只要对于任意的 $k$ 都满足 $A_k$ 可以操作之后得到 $B_k$,那么这个排列 $B$ 就是合法的。 这个是显然的。现在我们就可以直接状压 dp 了。考虑一个 $f_{k,s}$ 表示在 $k$ 时 $B_k$ 的值。那么转移要先判断 $B$ 是否合法,如果合法就直接枚举一个为 $1$ 的位置,然后转移过来。 判定合法是简单地,我们让 $A_k$ 和 $B_k$ 每个 $1$ 的位置按照前后顺序配对,只有所有的 $A$ 中 $1$ 的位置在配对的 $B$ 的位置之后,那么 $B$ 就是合法的。 ```cpp #include <bits/stdc++.h> using namespace std; #define ll long long #define il inline #define N 25 il int rd(){ int s = 0, w = 1; char ch = getchar(); for (;ch < '0' || ch > '9'; ch = getchar()) if (ch == '-') w = -1; for (;ch >= '0' && ch <= '9'; ch = getchar()) s = ((s << 1) + (s << 3) + ch - '0'); return s * w; } const int P = 1e9 + 7; int n, p[N], f[N][(1 << 20) + 5], popc[(1 << 20) + 5]; vector <int> Pos[N]; il bool check(int a, int b){return (!a) ? 1 : ((a & (-a)) < (b & (-b))) ? 0 : check(a ^ (a & (-a)), b ^ (b & (-b)));} signed main(){ freopen ("permutation.in", "r", stdin); freopen ("permutation.out", "w", stdout); n = rd(); for (int i = 0; i < n; i++) p[rd()] = i; f[n][0] = 1; for (int i = 1; i < (1 << n); i++) popc[i] = popc[i ^ (i & (-i))] + 1, Pos[popc[i]].push_back(i); for (int k = n - 1, S = (1 << p[k + 1]); ~k; k--, S |= (1 << p[k + 1])) for (int s : Pos[n - k]) if (check(s, S)) for (int i = 0; i < n; i++) if (((s >> i) & 1)) f[k][s] = (f[k][s] + f[k + 1][s ^ (1 << i)]) % P; printf ("%d\n", f[0][(1 << n) - 1]); return 0; } ```