[ZJOI2010]排列计数

注意观察题目:$Pi>Pi/2$。

发现特别像什么?

二叉堆

于是就变成了:$n$个堆元素进行排列,满足堆性质的排列对$p$的取模。(堆性质根据题意为大根堆)

设$f_i$为当前堆首为$i$的堆的排列方案数。为满足堆性质$P_i$显然只能取剩下若干数的最大值。

发现$f_i$影响$f_{2i}$和$f_{2i+1}$两个结点。

以$i$为根的堆去掉根节点的结点数为$n-i$个,提前统计出第$i$个结点左子树的大小$size_i$,则右边为$n-i-size_i$个结点。

根据前面,可以得到这样的转移:$f_i=C(n-i,\ size)\times f_{2i}\times f_{2i+1}$。

解释:从剩下的$n-i$个结点中选$size$个结点做其左子树,然后对于每个子树存在$f_{2i}$和$f_{2i+1}$中排列,根据乘法原理即得。

正确性:这些数是一个排列,也就是说任意两个数一定不相等,可以构成一个严格递增的序列。挑出的若干数及挑出若干数后仍能构成严格递增的序列,所以可以化成子问题。

边界:当前根结点为叶节点时,$f_i=1$。

最后答案:$f_1$。

注意:整个$DP$过程是$O(n)$的。但是存在计算组合数,复杂度为$O(n-i)$,$i$为当前根结点。根据递归主定理易知为$O(nlogn)$的复杂度。

存在模数,就要提前处理逆元。对于$p$过小还要用$Lucas$定理,然而数据水而且我不会所以只写了逆元。

 1 #include <bits/stdc++.h>
 2 
 3 using namespace std;
 4 
 5 #define re register
 6 #define rep(i, a, b) for (re int i = a; i <= b; ++i)
 7 #define repd(i, a, b) for (re int i = a; i >= b; --i)
 8 #define maxx(a, b) a = max(a, b);
 9 #define minn(a, b) a = min(a, b);
10 #define LL long long
11 #define inf (1 << 30)
12 
13 inline int read() {
14     int w = 0, f = 1; char c = getchar();
15     while (!isdigit(c)) f = c == '-' ? -1 : f, c = getchar();
16     while (isdigit(c)) w = (w << 3) + (w << 1) + (c ^ '0'), c = getchar();
17     return w * f;
18 }
19 
20 const int maxn = 1e6 + 5;
21 
22 int n, p, f[maxn << 1], g[maxn << 1], inv[maxn];
23 
24 int C(int n, int m) {
25     int res = 1;
26     repd(i, n, n-m+1) res = (LL)res * i % p;
27     rep(i, 1, m) res = (LL)res * inv[i] % p;
28     return res;
29 }
30 
31 int dfs(int u) {
32     if (u > n) return 0;
33     g[u] = dfs(u<<1);
34     return g[u] + dfs(u<<1|1) + 1;
35 }
36 
37 int dp(int u, int size) {
38     if (f[u]) return f[u];
39     if (u > n) return f[u] = 1;
40     return f[u] = (LL)dp(u<<1, g[u]) * dp(u<<1|1, size-g[u]-1) % p * C(size-1, g[u]) % p;
41 }
42 
43 int main() {
44     n = read(), p = read();
45     inv[1] = 1;
46     g[1] = 1;
47     rep(i, 2, n) {
48         if (i < p)
49             inv[i] = (LL)(p-p/i)*inv[p%i] % p;
50         else
51             inv[i] = inv[i % p];
52     }
53     dfs(1);
54     printf("%d", dp(1, n));
55     return 0;
56 }

 

posted @ 2019-02-12 21:45  AC-Evil  阅读(486)  评论(0编辑  收藏  举报