• [BZOJ2111]:[ZJOI2010]Perm 排列计数(组合数学)


    题目传送门


    题目描述

    称一个1,2,...,N的排列${P}_{1}$,${P}_{2}$,...,${P}_{N}$Magic的,当且仅当2≤i≤N时,${P}_{i}$>${P}_{frac{i}{2}}$计算1,2,...N的排列中有多少是Magic的,答案可能很大,只能输出模P以后的值。


    输入格式

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


    输出格式

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


    样例

    样例输入:

    20 23

    样例输出:

    16


    数据范围与提示

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


    题解

    来介绍两种做法:

    1.递推+组合数学:

     前缀知识:

       1)组合数学入门

       2)Lucas定理

     网上有好多人管这个叫DP,个人感觉不是DP,可能是一开始有一个叫它DP,于是好多半瓶子醋的就也叫它DP了叭

     首先,看到这道题我就想到这是一个小跟堆。

     你去想想,小跟堆是一个完全二叉树,所以每一个节点i的两个儿子分别是i>>1i>>1|1,所以P[i/2]其实就是P[i]的父亲,然后又因为P[i/2]<P[i],所以这就是一个小跟堆啦

     那么问题就转化为,求这n个数所能组成的小跟堆的数量。

     然后我们定义DP[i]表示i这个点为跟节点的方案数。

     对于没一个点的方案数,显然只能从他的两个儿子转移过来,即为:DP[i]=DP[i>>1]×DP[i>>1|1]

     但是显然答案不能直接这样的来,因为每个点i的两个子树中的数可以不一样,所以会漏掉答案。

     所以我们就要融入一些组合数学的问题了。

     因为只是交换两棵子树之间的数,每棵子树的size是不变的,所以我们就相当于是要乘上在size[leftson]+size[rightson]当中取size[leftson]个数的方案数。

     于是式子变为了:DP[i]=DP[i>>1]×DP[i>>1|1]×C(size[i]-1,size[i>>1])

     注意P是一个小的质数,所以考虑Lucas求组合数。

    2.拓扑排序:

     偶然间发现这道题就是在求一棵树的拓扑排序的数量(笔者正在证明)。

     直接套用公式:ans=$frac{n!}{prod limits_{i=1}^{n} size[i] }$

     接着就是推式子。

     DP[u]=[(size[u]-1)!×∏DP[v] ]/∏size[v](其中,vu的儿子)。

     接着定义g[u]=$frac{DP[u]}{size[u]!}$=$frac{prod g[v]}{size[u]}$=$frac{1}{prod size[k]}$(其中,ku子树上的点)。

     所以g[1]=$frac{1}{prod limits_{i=1}^{n} size[i] }$

     所以答案即为:ans=DP[1]=g[1]×size[1]!=$frac{n!}{prod limits_{i=1}^{n} size[i] }$

     但是注意数据加强之后可能会出现mod p=0的情况,注意附成1即可。


    代码时刻

    解法1

    #include<bits/stdc++.h>
    using namespace std;
    long long n,p;
    long long jc[1000001],qsm[1000001],dp[1000001],size[1000001];
    long long qpow(long long x,long long y)
    {
    	long long ans=1;
    	while(y)
    	{
    		if(y%2)ans=(ans*x)%p;
    		y>>=1;
    		x=(x*x)%p;
    	}
    	return ans;
    }
    void pre_work()
    {
    	jc[0]=1;
    	for(long long i=1;i<=n;i++)
    		jc[i]=(jc[i-1]*i)%p;
    	for(long long i=0;i<=n;i++)
    		qsm[i]=qpow(jc[i],p-2)%p;
    }
    long long get_C(long long x,long long y){return ((jc[x]*qsm[y])%p*qsm[x-y])%p;}
    long long lucas(long long x,long long y)
    {
    	if(!y)return 1;
    	return (get_C(x%p,y%p)*lucas(x/p,y/p))%p;
    }
    int main()
    {
    	scanf("%lld%lld",&n,&p);
    	pre_work();
    	for(long long i=n;i>0;i--)
    	{
    		if((i<<1)>n&&(i<<1|1)>n)
    		{
    			dp[i]=1;
    			size[i]=1;
    			continue;
    		}
    		if((i<<1|1)>n)
    		{
    			dp[i]=1;
    			size[i]=2;
    			continue;
    		}
    		size[i]=size[i<<1]+size[i<<1|1]+1;
    		dp[i]=lucas(size[i]-1,size[i<<1]);
    		if((i<<1)<=n)dp[i]=(dp[i]*dp[i<<1])%p;
    		if((i<<1|1)<=n)dp[i]=(dp[i]*dp[i<<1|1])%p;
    	}
    	cout<<dp[1]<<endl;
    	return 0;
    }
    

    解法2

    #include<bits/stdc++.h>
    using namespace std;
    long long n,p,s[1000001],ans=1,i,inv[1000001];
    int main()
    {
        scanf("%lld%lld",&n,&p);
        inv[1]=inv[0]=1;
        for(i=1;i<=n;i++)s[i]=1;
        for(i=n;i>=2;i--)s[i>>1]+=s[i];
        for(i=2;i<=n;i++)inv[i]=((1LL*(-p/i)*inv[p%i])%p+p)%p;
        for(i=1;i<=n;i++)
        {
        	ans=ans*i%p;
        	ans=max(ans,1LL);//%p=0附成1
        	ans=ans*inv[s[i]]%p;
        	ans=max(ans,1LL);//%p=0附成1
        }
        printf("%lld",ans);
        return 0;
    }
    

    rp++

  • 相关阅读:
    使用getattr() 分类: python基础学习 divide into python 2014-02-24 15:50 198人阅读 评论(0) 收藏
    使用locals()获得类,进行分发 分类: python 小练习 divide into python python基础学习 2014-02-21 14:51 217人阅读 评论(0) 收藏
    第1课第4.4节_Android硬件访问服务编写HAL代码
    第4.3节_Android硬件访问服务编写APP代码
    函数说明
    第1课第1节_编写第1个Android应用程序实现按钮和复选框
    vue生命周期
    程序代码中,怎么区分status和state?
    百度UEditor -- ZeroClipboard is not defined
    webstorm 设置ES6语法支持以及添加vuejs开发配置
  • 原文地址:https://www.cnblogs.com/wzc521/p/11119814.html
Copyright © 2020-2023  润新知