BZOJ 2339: [HNOI2011]卡农

本文介绍了一种通过补集转换解决计数问题的方法,并详细解释了如何计算特定条件下子集的方案数量。针对一个给定的问题,通过定义f[i]为使用i个盘子的方案数,利用递推公式来解决计数问题。

题目

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

分析

我们先把它变成有序的,最后除一个m!
对于有序的方案数我们考虑补集转换。
首先所有的子集个数应该是2^n-1;
我们定义f[i]为使用i个盘子的方案数。
因为要保证总数是偶数。也就是说如果你确定了i-1个盘子第i个盘子也就确定了
所以总数应该是A(2^n-1,i-1);
这样肯定多算了。具体来说有两部分。
1.如果前i-1个已经合法,那么第i个就是空集。这样肯定不合法。所以要减去f[i-1]
2.如果根据前i-1个确定出来的第i个集合和前面的某一个重复,这样肯定是不合法的。
对于第2种情况。
因为考虑顺序,所以那个和第i个重复的集合有i-1种位置.
对于每种位置。当前已经满足偶数了,去掉两个集合之后还是偶数。
所以剩下其他集合的方案数为f[i-2]。
然后我们需要算出有多少种可能重复的方案
因为我们已经确定了(i-2)个位置,所以方案数为(2^n-1-(i-2));
最后再乘上一个m!关于P的逆元即可。

代码

#include <bits/stdc++.h>

#define N 1000010  
#define P 100000007  

using namespace std;  

long long n,m,p[N],f[N],temp;  

long long power(long long a,long long b)  
{  
    long long ans(1);  
    for (long long i = b; i; i>>=1, (a *= a) %= P)
        if (i&1)
            (ans *= a) %= P;  
    return ans;  
}  

void pre()  
{  
    p[0] = 1;  
    for (long long i = 1; i <= m; i++)
        p[i] = (p[i - 1] * ((temp - i + 1 + P) % P)) % P;  
}

int main()  
{  
    scanf("%lld%lld",&n,&m);  
    temp = power(2,n);
    temp--;  
    if (temp < 0)
        temp += P;  
    pre();  
    for (long long i = 3; i <= m; i++)  
       f[i] = ((p[i - 1] - f[i - 1] - f[i - 2] * (i - 1) % P * (temp - (i - 2)) % P) + P) % P;  
    temp = 1;  
    for (long long i = 1; i <= m; i++)
        (temp *= i) %= P;  
    (f[m] *= power(temp, P - 2)) %= P;  
    cout<<f[m];  
}  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值