BZOJ 3426 && CodeChef/CHANGE

10 篇文章 0 订阅
1 篇文章 0 订阅

Description:

       经典问题,给你 N(N<=50) 种面值的硬币,每种可以使用无限次,第 i 硬币的面值为 Di(Di<=500) ,所有的 Di 互质,现在你要支付 C(C<=10100) 元钱,问有多少种支付方法。答案对 109+7 取模。多组数据,数据组数 T<=5

      
      
      

Solution:

       首先对于面值为 Di 的硬币构造多项式:

Fi=p=0+xDip

      
G(x)=i=1NFi

       那么显然把 G(x) xC 项的系数就是答案。
       但是此题 C 的范围异常的大,这让我们不能直接计算乘法之后的多项式,得考虑别的算法。
       根据幂级数求和,易得
Fi=11xDi

       所以
G(x)=i=1N11xDi=1(1x)Ni=1N11+x+...+xDi1

       G(x) 分式分解得:
G(x)=A(x)(1x)N+i=1NBi(x)1+x+...+xDi1=i=1N11xDi

       乱证明一下发现 A(x) 是一个次数小于 N 的多项式, Bi(x) 是一个次数小于 Di1 次的多项式。
       考虑多项式 Bi(x) ,那么有
      
Bi(x)1+x+...+xDi1=j=1N11xDjA(x)(1x)Nj=1,j!=iNBj(x)1+x+...+xDj1

      
Ri(x)=A(x)(1x)N+j=1,j!=iNBj(x)1+x+...+xDj1

       那么
Bi(x)1+x+...+xDi1=j=1N11xDjRi(x)

       将上式两边乘以左边分母有:
      
Bi(x)=j=1N11xDj×(1+x+...+xDj1)Ri(x)×(1+x+...+xDi1)

      
=1(1x)Nj=1,j!=i(1xDj)Ri(x)×(1+x+...+xDi1)

       那么对于 x=ωtDi (1<=t<Di) ,根据等比数列求和, 1+x+...+xDi1=0 ,因为所有的 Di 互质,当 x=ωtDi 时, Ri(x)×(1+x+...+xDi1)=0
       所以
Bi(ωtDi)=1(1ωtDi)Nj=1,j!=i(1ωt×DjDi)

       考虑使用等比数列求和可以十分容易的证明下式 (j!=0)
1Di×p=0Di1(p+1)ω p×jDi=11ω jDi

       所以
Bi(ωtDi)=(1Di)N×(p=0Di1(p+1)×ω p×tDi)×j=1,j!=iN(p=0Di1(p+1)×ω p×t×DjDi)

      
Bi(ωtDi)=(1Di)N×j=0Di1bj×ωjDi

       如果要求出 bi ,那么需要做 N 次形如下式的卷积运算(以下所有的指数和下标运算都是在模 Di 意义下进行的)
(p=0Di1apωpDi)×(p=0Di1(p+1)ωp×qDi)=p=0Di1cpωpDi

      
cp=k=0Di1(k+1)×apq×k=k=0Di1k×apq×k+k=0Di1ak=k=0Di2(k+1)×apq×(k+1)+k=0Di1ak

      
=k=0Di2(k+1)×a(pq)q×k+k=0Di1ak=k=0Di1(k+1)×a(pq)q×k+k=0Di1akDi×a(pq)q×(Di1)

      
=cpq+k=0Di1akDi×a(pq)q×(Di1)

       所以对于一次卷积运算,可以先求出 c0 ,通过不断的将下标加 q 求出下一个 c ,所以这样就能够在 O(Di) 的时间里计算一次卷积。所以可以在 O(N×Di) 的时间里求出 b0,b1,...,bDi1
       观察发现,对于不同的 t
Bi(ωtDi)=p=0Di2(rprDi1)×(ωtDi)p

       r0,r1,...,rDi1 都是相同的,对应等于 t=1 时的 b0,b1,...,bDi1
       由于 Bi(x) 是一个 Di2 次多项式,且对于 Bi(x) x=ωtDi(t=1,2..,Di1) 时都满足
Bi(ωtDi)=p=0Di2(rprDi1)×(ωtDi)p

       那么断定
Bi(x)=p=0Di2(rprDi1)×xp

       考虑
Bi(x)1+x+...+xDi1
对答案的贡献。
      
Bi(x)1+x+...+xDi1=Bi(x)×(1x)1xDi=Bi(x)×(1x)×p=0+xDi×p

       由于 Bi(x) 是一个 Di2 次多项式 ,显然 Bi(x)1+x+...+xDi1 xDi×u+v(0<=u,0<=v<Di) 的系数为 (rvrDi1)(rv1rDi1)=rvrv1
       所以把 C 对于 Di 取模后就可以算出 Bi(x)1+x+...+xDi1 对答案的贡献了。
       所以求出所有 Di(x) 对答案的贡献的时间复杂度是 O(N2×D)
       之后 A(x)(1x)N 对答案的贡献,乱证明一下发现 A(x) 是一个多项式,并且每一项的系数都是常数。
       并且
1(1x)N=i=0+h(i)xi

       其中 h(x) 是一个关于 x N1 次多项式
       又因为 A(x) 是一个系数为常数的多项式,所以可以断定
      
A(x)(1x)N=i=0+S(i)xi

       其中 S(x) 是一个关于 x N1 次多项式
       由于我们能够求出 Bi(x) 对于答案的贡献,并且对于 C 比较小的时候的答案我们是可以 dp 求出的,那么就可以求出 C 比较小的时 A(x)(1x)N 对答案的贡献。
       所以先用 dp 求出 C=0,1,2,...,N1 时的答案,然后求出 Bi(x) 的贡献,那么就可以求出 S(0),S(1),...,S(N1) ,然后插值或者高斯消元可以解出多项式 S(x) 各项的系数。之后就得出了 S(x) 的表达式 。之后计算对于题目给出的 C 的答案, S(C) 就是 A(x)(1x)N 对答案的贡献。我用的是高斯消元,这部分的时间复杂度是 O(N3)
       至此此题终结。
      
      
      

Code:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>

using namespace std;

const long long Mod=1e9+7;

int N;
int T;
int D[60]={0};
long long B[60][510]={{0}};
long long help[510]={0};
long long A[110]={0};
long long F[110]={0};
long long fc[110][110]={{0}};

struct Bignum
{
    int cnmbdctr[200];
}C={0};

void read_(struct Bignum &R)
{
    int len=0;
    char ch=getchar();
    for(;ch<'0' || ch>'9';ch=getchar());
    for(;ch>='0' && ch<='9';ch=getchar())
        R.cnmbdctr[++len]=ch-'0';
    for(int i=(len>>1);i>=1;i--)
        swap(R.cnmbdctr[i],R.cnmbdctr[len-i+1]);
    R.cnmbdctr[0]=len;
    return;
}

int Module(Bignum R,long long MM)
{
    long long remains=0;
    for(int i=R.cnmbdctr[0];i>=1;i--)
        remains=(remains*10+R.cnmbdctr[i])%MM;
    return (int)remains;
}

long long power(long long a,long long k)
{
    long long o=1;
    for(;k>0;k>>=1)
    {
        if(k&1) o=o*a%Mod;
        a=a*a%Mod;
    }
    return o;
}

void Guass()
{
    for(int i=0;i<=N;i++)
    {
        int gg=i;
        for(;gg<=N;gg++)
            if(fc[gg][i]!=0) break;
        if(gg==N+1) continue;
        swap(fc[i],fc[gg]);
        long long Inv=power(fc[gg][i],Mod-2);
        for(int j=i;j<=N+1;j++)
            fc[gg][j]=fc[gg][j]*Inv%Mod;
        for(int j=0;j<=N;j++)
        {
            if(j==i) continue;
            if(fc[j][i]!=0)
            {
                long long mult=fc[j][i];
                for(int p=i;p<=N+1;p++)
                    fc[j][p]=(fc[j][p]-fc[gg][p]*mult%Mod+Mod)%Mod;
            }
        }
    }
    return;
}

long long getansb(int i,int PP)
{
    if(PP==0)
        return (B[i][0]-B[i][D[i]-1]+Mod)%Mod;
    else return (B[i][PP]-B[i][PP-1]+Mod)%Mod;
}

int main()
{
    cin>>T;
    for(;T>0;T--)
    {
        cin>>N;
        memset(C.cnmbdctr,0,sizeof(C.cnmbdctr));
        read_(C);
        for(int i=1;i<=N;i++)
            scanf("%d",&D[i]);
        memset(B,0,sizeof(B));
        memset(help,0,sizeof(help));
        for(int i=1;i<=N;i++)
        {
            long long Inv=power(Mod-D[i],Mod-2);
            Inv=power(Inv,N);
            for(int j=0;j<D[i];j++)
                B[i][j]=j+1;
            for(int p=1;p<=N;p++)
            {
                if(p==i) continue;
                long long Sum=0;
                for(int j=0;j<D[i];j++)
                {
                    help[j]=B[i][j];
                    B[i][j]=0;
                    Sum=(Sum+help[j])%Mod;
                }
                for(int j=0;j<D[i];j++)
                    B[i][0]=(B[i][0]+(j+1)*help[(D[i]-j*D[p]%D[i])%D[i]])%Mod;
                int Cnt=0;
                for(int j=1;j<D[i];j++)
                {
                    int Next=(Cnt+D[p])%D[i];
                    B[i][Next]=(B[i][Cnt]+Sum-(D[i]*help[(Cnt-D[p]*(D[i]-1)%D[i]+D[i])%D[i]])%Mod+Mod)%Mod;
                    Cnt=Next;
                }
            }
            for(int j=0;j<D[i];j++)
                B[i][j]=B[i][j]*Inv%Mod;
        }
        memset(F,0,sizeof(F));
        F[0]=1;
        for(int i=1;i<=N;i++)
        {
            for(int j=D[i];j<=N+20;j++)
                F[j]=(F[j]+F[j-D[i]])%Mod;
        }
        memset(fc,0,sizeof(fc));

        for(int i=0;i<=N;i++)
        {
            fc[i][0]=1;
            long long Cnt=i;
            for(int j=1;j<=N;j++)
            {
                fc[i][j]=Cnt;
                Cnt=Cnt*i%Mod;
            }
            fc[i][N+1]=F[i];
            for(int j=1;j<=N;j++)
                fc[i][N+1]=(fc[i][N+1]-getansb(j,i%D[j])+Mod)%Mod;
        }
        Guass();
        long long Ans=0;
        for(int i=1;i<=N;i++)
        {
            int PP=Module(C,(long long)D[i]);
            Ans=(Ans+getansb(i,PP))%Mod;
        }
        long long Cnt=1;
        for(int j=0;j<=N;j++)
        {
            Ans=(Ans+fc[j][N+1]*Cnt)%Mod;
            Cnt=Cnt*Module(C,Mod)%Mod;
        }
        cout<<Ans<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值