首页 > 其他 > 详细

[HNOI2011]卡农

时间:2019-11-22 14:29:39      阅读:80      评论:0      收藏:0      [点我收藏+]

题目链接:Click here

Solution:

题面讲的我一脸懵,我们先来简化一下题目意思:求在\([1,2^n-1]\)内选择\(m\)个数使得她们异或和为0的方案数

直接求是不那么好求的,正难则反,我们考虑通过不合法的方案数来求,设\(f[i]\)表示选了\(i\)个数的合法排列

由于要异或和为0,所以事实上我们选了\(i-1\)个之后,第\(i\)个就已经确定了,所以总排列数为\(A_{2^n-1}^{i-1}\)

考虑不合法的情况分成2种:选了0和选了已经重复的

假如选了0,说明这\(i-1\)个数异或和为0,则我们需要减去\(f[i-1]\)

加入我们选了一个重复的,则剩下的\(i-2\)个异或和为0,我们枚举那个数和位置,则减\(f[i-2]\times (2^n-1-(i-2))\times (i-1)\)

即:
\[ f[i]=A_{2^n-1}^{i-1}-f[i-1]-f[i-2]\times (2^n-1-(i-2))\times (i-1) \]

最后,答案即为\(f[m]\)除以\(m!\),那为什么要求排列而不直接求组合呢?

因为组合数我们是不能\(O(m)\)递推的,而排列数可以,所以我们先求出排列数,再算答案

Code:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+11;
const int mod=1e8+7;
int n,m,A[N],f[N];
int qpow(int a,int b){
    int re=1;
    while(b){
        if(b&1) re=(re*1ll*a)%mod;
        b>>=1;a=(a*1ll*a)%mod;
    }return re;
}
int read(){
    long long x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-f;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
    return x*f;
}
signed main(){
    n=read(),m=read();int ifac=1;
    int Val=qpow(2,n);--Val;f[0]=1;A[0]=1;
    if(Val<m) return puts("0"),0;
    for(int i=1;i<=m;i++) A[i]=(A[i-1]*1ll*(Val-i+1))%mod;
    for(int i=1;i<=m;i++) ifac=(ifac*1ll*i)%mod;
    ifac=qpow(ifac,mod-2);
    for(int i=2;i<=m;i++){
        int sum=A[i-1],u=(Val+mod-(i-2))%mod;
        f[i]=(sum+mod-f[i-1]-((((u*1ll*f[i-2])%mod)*1ll*(i-1))%mod))%mod;
    }
    printf("%lld\n",(f[m]*1ll*ifac)%mod);
    return 0;
}

[HNOI2011]卡农

原文:https://www.cnblogs.com/NLDQY/p/11910884.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!