zoukankan      html  css  js  c++  java
  • BZOJ2111: [ZJOI2010]Perm 排列计数

    BZOJ2111: [ZJOI2010]Perm 排列计数

    Description

    称一个1,2,...,N的排列P1,P2...,Pn是Magic的,当且仅当2<=i<=N时,Pi>Pi/2.

    计算1,2,...N的排列中有多少是Magic的,答案可能很大,只能输出模P以后的值

    Input

    输入文件的第一行包含两个整数 n和p,含义如上所述。

    Output

    输出文件中仅包含一个整数,表示计算1,2,⋯, ���的排列中, Magic排列的个数模 p的值。

    Sample Input

    20 23

    Sample Output

    16

    HINT

    100%的数据中,1 ≤ ��� N ≤ 106, P��� ≤ 10^9,p是一个质数。 数据有所加强


    题解Here!
    乍一看,感觉像一个数位$DP$。
    然而本蒟蒻看完题解,发现了骚操作:
    首先有个莫名其妙的定理:对于一个完全二叉树,如果父亲点权总是比儿子点权小,则构成了一个小根堆。
    于是题目的意思就是:求有多少个大小为$n$的小根堆。
    妙啊!
    考虑动态规划求解。
    设$dp[i]$表示有多少个大小为i的小根堆。
    转移时考虑剩下的$i-1$个点中有$C_{i-1}^l$个点能作为左子树,剩下的点作为右子树。
    即:$$dp[i]=C_{n-1}^l imes dp[l] imes dp[r]$$
    组合数取模,$pin prime$,果断卢卡斯。
    然后并不知道为什么$WA$了两发。。。
    附代码:
    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #define MAXN 1000110
    using namespace std;
    int n;
    long long m,p;
    long long fact[MAXN],inv[MAXN],val[MAXN],dp[MAXN];
    inline int read(){
    	int date=0,w=1;char c=0;
    	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
    	while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
    	return date*w;
    }
    long long mexp(long long a,long long b,long long c){
    	long long s=1;
    	while(b){
    		if(b&1)s=s*a%c;
    		a=a*a%c;
    		b>>=1;
    	}
    	return s%c;
    }
    long long lucas(int n,int m,long long p){
    	if(n<m)return 0;
    	if(m>=p||n>=p)return lucas(n/p,m/p,p)%p*lucas(n%p,m%p,p)%p;
    	return (fact[n]*inv[n-m]%p*inv[m]%p)%p;
    }
    void work(){
        for(int i=n;i>=1;i--){
            val[i]=1;
            if((i<<1)<=n)val[i]+=val[i<<1];
            if((i<<1)+1<=n)val[i]+=val[(i<<1)+1];
            if((i<<1)+1<=n)dp[i]=lucas(val[i]-1,val[i<<1],p)%p*dp[i<<1]%p*dp[(i<<1)+1]%p;
            else if((i<<1)<=n)dp[i]=dp[i<<1]%p;
            else dp[i]=1;
        }
        printf("%lld
    ",dp[1]);
    }
    void init(){
        n=read();p=read();
        m=min((long long)n,p-1);
        fact[0]=1;
        for(int i=1;i<=m;i++)fact[i]=fact[i-1]*i%p;
        inv[m]=mexp(fact[m],p-2,p);
        for(int i=m-1;i>=0;i--)inv[i]=inv[i+1]*(i+1)%p;
    }
    int main(){
        init();
        work();
        return 0;
    }
    
  • 相关阅读:
    UIFont的使用和字体类型总结
    LOJ-10100(割点个数)
    LOJ-10099(点双联通)
    poj-3177(并查集+双联通分量+Tarjan算法)
    图论:割点和桥
    牛客训练五:炫酷数学(思维)
    牛客训练五:炫酷路途(c++与dp)
    并查集的两种实现(按秩合并+路径压缩)
    牛客训练六:海啸(二维树状数组+vector函数的使用)
    牛客训练六:美食(贪心)
  • 原文地址:https://www.cnblogs.com/Yangrui-Blog/p/9482655.html
Copyright © 2011-2022 走看看