[BZOJ 2339][HNOI 2011]卡农(组合数学)

142 篇文章 0 订阅
98 篇文章 0 订阅

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=2339

思路

深感自己的数学有多么的弱。。。。完了完了。。。赶快回去补MO去
先暂时修改下题意,排列不同的方案看成不同的方案,比如{{1,2},{3,4}}和{{3,4},{1,2}}是不同的方案,这样用排列数就没有除法的问题。
f[i] 来表示前 i 段的合法方案数,g[i]=Ai2n1=(2n1)(2n2)...(2ni), g[i] 就是排列数(一直认为排列数是P,然后发现现在国内的课本居然强行改成了A,what the fuck!!!!)
那么我们可以采取补集转换的思想,在推出 f[i] 时,我们可以用前 i1 段的所有方案数,包括不合法方案,就是一个大小为 n 的集合中选i1个互不相同的子集的方案数,为什么要选的是互不相同的子集呢,这是题目的限制,也就是 Ai1...(i1)2n1...() ,然后减去不合法的那些方案,不合法的方案有两种:
1、前 i1 段的方案是合法的,这时前 i1 段,每种音调的个数是偶数个,而加入第 i 段后,由于第i段中每种不同的音调都只能出现 1 次,那么加入第i段后每种音调的个数可能出现奇数个,就不合法了,这种情况的方案数有 f[i1]
2、第 i 段和前i1段中某一段是相同的,为了便于叙述,我们说前 i1 段中与第 i 段相同的段是第i段的冲突对象,那么冲突对象可以在1到 i1 号段中选一个,有 (i1) 种选法,选好冲突对象后,剩下的 i2 个不冲突的段的方案数是 f[i2] (有2个子集被去掉了,某些种音调里去掉了偶数次,现在每种音调的出现次数就是偶数次,那么剩下的段肯定是合法的),第 i 段和其冲突对象对应的子集有(2n1)(i2),解释: n 个音调的子集总数共有(2n1)种,然后前 i1 段中有 i2 个不和第 i 段相同的段已经确定下来了,占用了其中的i2个子集,因此前面的式子是 (2n1)(i2) ,因此乘法计数可以得到二号情况的方案数是 f[i2](i1)[(2n1)(i2)]
那么就能得到初步的一个递推式:
f[i]=Ai12n1f[i1]f[i2](i1)[(2n1)(i2)]
然后用 g[i] 代进去替换掉那个A
f[i]=g[i1]f[i1]f[i2](i1)[(2n1)(i2)]
恩。。。现在比较好搞了,最终很容易推出 f[n] ,然后因为题目本来是要求排列不同的方案看成相同的方案,那么就除以 m! 就可以了,但是模意义下不能做除法,所以用扩欧求出模xxx意义下 m! 的乘法逆元,乘上 f[n] 就是最终答案。
呼。。。这篇题解恐怕是我写得最长的题解了,真TM蛋疼。。。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>

#define MAXN 1000100
#define MOD 100000007

using namespace std;

typedef long long int LL;

LL n,m;
LL power[MAXN]; //power[i]=2^i
LL g[MAXN],f[MAXN],ans; //f[i]=前i段合法的方案数,g[i]=A(2^n-1,i)

LL fastPow(LL base,LL pow)
{
    LL ans=1;
    while(pow)
    {
        if(pow&1) ans=ans*base%MOD;
        base=base*base%MOD;
        pow>>=1;
    }
    return ans;
}

LL extGCD(LL a,LL b,LL &x,LL &y) //ax+by=1
{
    if(b==0)
    {
        x=1;
        y=0;
        return a;
    }
    LL tmp=extGCD(b,a%b,x,y);
    LL t=x;
    x=y;
    y=t-(a/b)*y;
    return tmp;
}

LL reverse(LL a,LL b) //求模b意义下a的逆元x,ax=1(mod b),b是质数所以可以求逆元(gcd(a,b)=1)
{
    LL x,y;
    extGCD(a,b,x,y);
    x=(x%b+b)%b;
    return x;
}

void init()
{
    LL i,a=fastPow(2,n)-1;
    g[0]=1;
    for(i=1;i<=m;i++) g[i]=g[i-1]*(a-i+1)%MOD;
}

int main()
{
    scanf("%lld%lld",&n,&m);
    init();
    LL a=fastPow(2,n)-1,i;
    f[1]=f[2]=0;
    for(i=3;i<=m;i++)
    {
        f[i]=g[i-1]-f[i-1]-f[i-2]*(i-1)%MOD*(a-(i-2))%MOD;
        f[i]%=MOD;
    }
    if(f[m]<0) f[m]+=MOD;
    a=1;
    for(i=1;i<=m;i++) a=a*i%MOD;
    a=reverse(a,MOD);
    f[m]=f[m]*a%MOD;
    printf("%lld\n",f[m]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值