51nod 1778 小Q的集合

1778 小Q的集合

原题连接
https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1778
题目绕了一点。并没有直接给出数字形式。
而且这个题有好像有很多方法。
我也YY了一个(不过还是题解多效率高)。

题目中是给定了一个集合 S 并且有 S=n

要说明的是。这个集合是没有重复元素的集合。(这一点很重要)
其实不特别说明。一般集合也都没有重复元素。

集合S的子集数量等价于 从 S 中取元素的取法数量。(高中数学)

记 子集数量为 x

x=i=0n(ni)=1i1ni(ni)=(1+1)n=2n

那么我们对所有子集进行编号。 记第i个子集为 Ti
因为集合没有重复元素。 那么有:
任意 Ti 都一一对应 一个 补。即: STi
也就是有:
i=1xTi=i=1xSTi
更一般的:
i=1xf(Ti)=i=1xf(STi)
那么。令原题中的期望为 E :

xE=2nE=i=1x[ f(Ti)f(STi) ]=i=1xf(Ti)i=1xf(STi)=i=1xf(Ti)i=1xf(Ti)=0

所以。方差 v 也就有:

answer=2nv=xv=xσ2=i=1x[ f(Ti)f(STi)E]2=i=1x[ f(Ti)f(STi)]2

我们更加注意集合元素的数量。而对于包含 i 个元素的集合数量是:

(ni)

所以:
answer=i=0n[ik(ni)k]2(ni)% m
对于这个和式是否有封闭形式。 我不知道。不过有就是结论提了。哈哈.
令 :
G(i)=[ik(ni)k]2(n%mi%m)
mod  m 的意义下(卢卡斯定理)
answer=i=0nG(i)(nmim)
观察 G(i) 有:
G(i+m)=[(i+m)k(n(i+m))k]2=G(i)   (mod  m)
记 :
S=i=0nG(i)
记:
S(l,r)=i=lrG(i)    i>mi<0G(i)=0
则有:
answer=i=0nG(i)(nmim)=i=0nmb=0m1[G(im+b)(nmi)]S(n%m+1 , n+1)
其中
b=0m1[G(im+b)(nmi)]=(nmi)b=0m1G(im+b)=(nmi)S
漂亮 。这样就有:
i=0nmb=0m1[G(im+b)(nmi)=Si=0nm(nmi)=S2nm
哈哈哈哈:
answer=S2nmS(n%m+1 ,n+1)
这是一个很简洁的答案。 但是由于快速幂 比较耗时间。 要细心些哦。
当然。我们必须要结合费马小定理来进一步优化:
a(p1)=1   (mod  p)
因为m为素数,所以:

2nm=2(nm %(m1))   (mod  m)

下面是代码:

#include <stdio.h>
#define MAXN 1000005

typedef long long LL;
LL n,m,k,r;

LL pow(LL,int);
LL clat_divie();// 计算n/m 返回余数,商 保存在N[]
void init();//预处理逆元
LL G[MAXN];
int S[MAXN];
LL C[MAXN];
LL Ivn[MAXN];
LL Po[MAXN];
char s[MAXN];
int N[MAXN],size;

int main ()
{
    //freopen("/Users/gaoxusheng/Desktop/51nod_Problem_1778_Test_25_In.txt","rw",stdin);
    scanf("%s",s);
    scanf("%lld %lld",&k,&m);
    init();
    r=n=clat_divie();
    C[0]=1;
    for(int i=0;i<m;i++)C[i+1]=C[i]*(r-i)*Ivn[i+1]%m;
    for(int i=0,sz=(int)m+1;i<sz;i++)
    {
        LL &tmp=Po[i];
        tmp=1;
        LL u=i;
        int b=(int)k;
        while(b)
        {
            if(b&1)
                tmp=tmp*u%m;
            u=u*u%m;
            b>>=1;
        }
    }
    for(int i=0;i<=m;i++)
    {
        G[i]=Po[i]*(Po[i]-Po[n--]+m);
        if(n<0)n+=m;
        G[i]*=C[i];
        G[i]%=m;
    }
    int M=(int)m-1;
    for(int i=(int)m;i>-1;i--)
    {
        S[i]=(S[i+1]+(int)G[i]);
        if(S[i]>M)S[i]-=m;
    }
    LL tmp=0;
    m--;
    for(int i=0;i<size;i++)
    {
        tmp=tmp*10+N[i];
        if(tmp>=m)  tmp%=m;
    }
    m++;
    LL ans=(LL)S[0]*pow(2,(int)tmp)%m;
    ans-=(LL)S[r+1];
    if(ans<0)ans+=m;
    printf("%lld\n",(ans<<1)%m);
    return 0;
}
void init()
{
    Ivn[1]=1;
    for(int i=2;i<m;i++)
        Ivn[i]=m-Ivn[m%i]*(m/i)%m;
}

LL pow(LL a,int b)
{
    LL tmp=1;
    while(b)
    {
        if(b&1)
            tmp=tmp*a%m;
        b>>=1;
        a=a*a%m;
    }
    return tmp;
}

LL clat_divie()
{
    LL tmp=0;
    for(int i=0;s[i];i++)
    {
        tmp=tmp*10+s[i]-'0';
        if(tmp>=m)
        {
            N[size]=(int)tmp/m;
            tmp%=m;
        }
        size++;
    }
    bool flag=true;
    for(int i=0;i<size;i++)
        if(N[i]>0)
        {
            flag=false;
            int t;
            for(t=0;i<size;i++,t++) N[t]=N[i];
            size=t;
            break;
        }
    if(flag)size=0;
    return tmp;

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值