其他分享
首页 > 其他分享> > HNOI2011 卡农

HNOI2011 卡农

作者:互联网

题目链接:戳我

蒟蒻今天正式开始学容斥了!!(但是还是不会做啊不会做啊不会做啊)

这个题题意化简下来是这样的:n个数中选择m个子集,它们需要满足三个条件——
1、选择的子集不能为空
2、选择的子集不能相同
3、选择的子集中,每个数出现的次数必须为偶数
那个集合相同的条件没有什么难搞的,直接算出来答案之后,除以m!即可(因为有m!种排列顺序嘛)

然后还有一种题意解释:
在\(2^n-1\)个数中,选择m个数,使得它们的异或和为0;(限制条件参考上面三个)

我们设\(dp[i]\)表示选择i个数,它们的异或和为0。

然后因为第三个条件,所以我们知道如果确定了前i-1个数,那么第i个也就确定下来了(选择原先出现的奇数次的数即可),所以总的选择种类为\(A_{2^n-1}^{i-1}\)。

但是这样的话里面存在不合法情况。第一种这一次选择的子集为空了。那么这样的话表现出来的就是原先i-1个数的异或和为0。所以我们要减去\(dp[i-1]\)

第二种是选择的子集相同了。我们枚举这次选择的数(和前面有一个数重复了),这个数到底是什么。这样的话一共有\(2^n-1-(i-2)\)种数可以选择。当然我们也要考虑到到底和前面哪一个数重复了,所以要乘上\((i-1)\),因为重复了,两次异或等于没有操作,所以i-2个数的异或和是0,所以最后乘上\(dp[i-2]\)。

所以最后的递推式就是\(dp[i]=A_{2^n-1}^{i-1}-dp[i-1]-dp[i-2]*(i-1)*(2^n-1-(i-2))\)

至于为什么对\(dp[0]=1,dp[1]=0\)这样子初始化。大家可以考虑手算一下n=3,n=2的dp值,然后根据答案以及系数就可以推出来dp[0]和dp[1]了。

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 1000010
#define mod 100000007
using namespace std;
int n,m;
long long k=1;
long long dp[MAXN],A[MAXN];
inline long long pow(int x,int y)
{
    if(y==0) return 1;
    long long cur_ans=1;
    while(y)
    {
        if(y&1) cur_ans=1ll*cur_ans*x%mod;
        x=1ll*x*x%mod;  
        y>>=1;
    }
    return cur_ans;
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("ce.in","r",stdin);
    #endif
    scanf("%d%d",&n,&m);
    long long maxx=pow(2,n)-1;
    maxx%=mod;
    A[1]=maxx;
    dp[0]=1,dp[1]=0;
    for(int i=2;i<=m;i++) A[i]=1ll*A[i-1]*(maxx-i+1)%mod;
    for(int i=1;i<=m;i++) k=1ll*k*pow(i,mod-2)%mod;
    for(int i=2;i<=m;i++)
        dp[i]=(A[i-1]-dp[i-1]-((1ll*dp[i-2]*(i-1)%mod)*(maxx-(i-2)))%mod+mod)%mod;
    printf("%lld\n",1ll*dp[m]*k%mod);
    return 0;
}

标签:int,个数,异或,long,选择,HNOI2011,卡农,dp
来源: https://www.cnblogs.com/fengxunling/p/10415063.html