题解 P5148 【大循环】

· · 题解

观察代码部分可以发现ans=循环次数*f(q)

处理循环次数:

1.显然的一点是a[1]>a[2]>...>a[k],即从a[1]到a[k]严格单减,因为a[k]最小为1,所以a[1]最小为k,最大为n;

2.开始递推:

先举个例子,设n=7,k=4;

a1=4时,a2~a4分别为3,2,1;

a1=5时,a1~a4可能为,{4,3,2},{4,3,1},{4,2,1},{3,2,1},即从4,3,2,1中扔掉一个数,剩下的三个数即为a2~a4,方案数为C(4,1);

a1=6时,从5,4,3,2,1中选出两个数扔掉,剩下三个数即为a2~a4,方案数为C(5,2);

a1=7时,从6,5,4,3,2,1中选出三个数扔掉,剩下三个数即为a2~a4,方案数为C(6,3);

于是a1=4时的方案数可看做C(3,0);

所以方案数=C(3,0)+C(4,1)+C(5,2)+C(6,3)=C(7,4);

(以下是计算过程:

ans=C(3,0)+C(4,1)+C(5,2)+C(6,3)=C(4,0)+C(4,1)+C(5,2)+C(6,3)=C(5,1)+C(5,2)+C(6,3)=C(6,2)+C(6,3)=C(7,3)=C(7,4)

应用性质:C(n+1,m)=C(n,m)+C(n,m-1);)

所以对于给定的n与k: a1=k时,方案数C(k-1,0);

a1=k+1时,方案数C(k,1);

a1=k+2时,方案数C(k+1,2);

a1=k+3时,方案数C(k+2,3);

......

a1=n时,方案数为C(n-1,n-k);

(为啥这里是C(n-1,n-k),一共有n-1个元素,需要留下k-1个,所以需要扔掉(n-1)-(k-1)个元素,即扔掉n-k个元素)

所以方案总数=C(k-1,0)+C(k,1)+C(k+1,2)+...+C(n-1,n-k)=C(n,n-k)=C(n,k);

C(n,k)用阶乘求即可,卢卡斯在这里基本没用,因为n和k都小于1e9+7;当然,不要忘了求逆元;

然后就剩下f(q)了

暴力算乘方即使是快速幂都会很慢,但是这个式子可以通过提公因式转化一下;

还是先举个栗子:

f(q)=a3·x^3+a2·x^2+a1·x+a0
    =(a3·x^2+a2·x+a1)·x+a0
    =[(a3·x+a2)·x+a1]·x+a0

其实这个就是著名的秦九韶(shao2)算法,代码实现在下文中会给出;

最后附上自己的代码

    #include<iostream>
    #include<cstdio>
    #define ll long long
    using namespace std;
    const long long mod=1000000007;
    long long n,m,k,q,a[500010],ans;
    ll ksm(ll a,ll b)
    {   
            ll ret=1;
            while(b)
        {
            if(b&1) ret=ret*a%mod;
            b>>=1;
            a=a*a%mod;
        }
        return ret;
    }
    ll C()
    {
        ll a=1,b=1;
        for(ll i=n;i>=n-k+1;--i) a=a*i%mod;
        for(ll i=2;i<=k;++i) b=b*i%mod;
        return a*ksm(b,mod-2)%mod;
            //根据费马小定理,若a与p互质,那么a%p的逆元就是a^p-2,所以用快速幂求a%p的逆元
    }
    int main()
    {
        scanf("%lld%lld%lld%lld",&n,&m,&k,&q);
        q=q%mod;//不加这一句会少20分,很玄学
        for(int i=0;i<=m;++i) scanf("%lld",&a[i]);
        //秦九韶算法
        for(ll i=m;i>0;--i) ans=(ans+a[i])*q%mod;
        ans=(ans+a[0])%mod;//别忘了加上a0
        ans=ans*C()%mod;
        printf("%lld",ans);
        return 0;
    }

组合数见高中数学选修3-3

秦九韶算法见高中数学必修3