• BZOJ 2111 [ZJOI2010]Perm 排列计数:Tree dp + Lucas定理


    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2111

    题意:

      给定n,p,问你有多少个1到n的排列P,对于任意整数i∈[2,n]满足P[i]>P[i/2]。

      保证p为质数,输出答案 mod p的值。(n <= 10^6, p <= 10^9)

    题解:

      对于每个i,分别向i*2和i*2+1连一条边。

      可以发现,最终形成的是一棵以1为根节点的二叉树。

      题目中P[i]>P[i/2]的条件,就变成了:P[fa]<P[son]

      然后就可以dp了。

      表示状态:

        dp[i]表示对于i的子树来说,填入1到siz[i]这些数,并且满足条件的方案数。

      找出答案:

        ans = dp[1]

      如何转移:

        对于i的子树来说,显然节点i只能填1。

        所以首先考虑的就是将2到siz[i]这些数分配给两个子树的方案数。

        设l = i*2, r = i*2+1,则方案数显然为C(siz[i]-1, siz[l])。

        所以dp[i] = C(siz[i]-1, siz[l]) * dp[l] * dp[r]

      边界条件:

        dp[leaf] = siz[leaf] = 1

      因为dp转移中要求组合数:C(n,m) = fact[n] * inv(fact[m]) * inv(fact[n-m])

      然而给定的p可能很小,以至于与要求逆元的数不互质。

      所以要用到Lucas定理求组合数:C(n,m)%p = C(n%p,m%p) * lucas(n/p,m/p) % p

    AC Code:

     1 #include <iostream>
     2 #include <stdio.h>
     3 #include <string.h>
     4 #define MAX_N 1000005
     5 #define int ll
     6 
     7 using namespace std;
     8 
     9 typedef long long ll;
    10 
    11 int n,p;
    12 int f[MAX_N];
    13 int dp[MAX_N];
    14 int siz[MAX_N];
    15 
    16 void cal_f()
    17 {
    18     f[0]=1;
    19     for(int i=1;i<=n;i++) f[i]=f[i-1]*i%p;
    20 }
    21 
    22 void exgcd(int a,int b,int &x,int &y)
    23 {
    24     if(b==0)
    25     {
    26         x=1,y=0;
    27         return;
    28     }
    29     exgcd(b,a%b,y,x);
    30     y-=(a/b)*x;
    31 }
    32 
    33 int inv(int a)
    34 {
    35     int x,y;
    36     exgcd(a,p,x,y);
    37     return (x%p+p)%p;
    38 }
    39 
    40 int c(int n,int m)
    41 {
    42     if(n<m) return 0;
    43     return f[n]*inv(f[m])%p*inv(f[n-m])%p;
    44 }
    45 
    46 int lucas(int n,int m)
    47 {
    48     if(m==0) return 1;
    49     return c(n%p,m%p)*lucas(n/p,m/p)%p;
    50 }
    51 
    52 void dfs(int x)
    53 {
    54     dp[x]=siz[x]=1;
    55     int l=(x<<1),r=((x<<1)|1);
    56     if(l<=n) dfs(l),siz[x]+=siz[l],dp[x]=dp[x]*dp[l]%p;
    57     if(r<=n) dfs(r),siz[x]+=siz[r],dp[x]=dp[x]*dp[r]%p;
    58     if(l<=n) dp[x]=dp[x]*lucas(siz[x]-1,siz[l])%p;
    59 }
    60 
    61 signed main()
    62 {
    63     cin>>n>>p;
    64     cal_f();
    65     dfs(1);
    66     cout<<dp[1]<<endl;
    67 }
  • 相关阅读:
    Android设置RadioButton在文字的右边
    如何创建启动界面Splash Screen
    sqlite3 数据类型 批量插入
    为PopupWindow设置弹出动画效果
    android activity生命周期
    Eclipse快捷键大全
    SQLite的使用
    创建窗口式Activity
    Android中实现按钮自动隐藏
    android技术片段
  • 原文地址:https://www.cnblogs.com/Leohh/p/8546860.html
Copyright © 2020-2023  润新知