BZOJ5339: [TJOI2018]教科书般的亵渎-组合数学

传送门

题意:

在炉石传说中有这样的一个场面:n个随从,血量为1~n,现在去除m个随从,然后开始释放“亵渎”。每使用一张“亵渎”会获得一定的分数,分数计算如下:在使用一张“亵渎”之后,每一个被亵渎造成伤害的怪会产生 xk x k 的分数,其中x是造成伤害前怪的血量,k是需要杀死所有怪物所需的“亵渎”的张数。

n1013m50 n ≤ 10 13 m ≤ 50

Solution:

可以发现这是若干个形如 Sum(n,k)=ni=1ik S u m ( n , k ) = ∑ i = 1 n i k 的式子组成的

这就是经典的自然数幂和问题了

可以用累加法:

首先有一个式子: (n+1)knk=ki=1Ciknki ( n + 1 ) k − n k = ∑ i = 1 k C k i ∗ n k − i

左边组合意义是k个球涂n+1种颜色的方案数和k个球涂n种颜色的方案数

右边组合意义是在k个球选一些球涂第n+1种颜色,其他球随便涂的方案数

然后我们可以通过上面的式子列出若干式子:

(n+1)knk=ki=1Ciknki ( n + 1 ) k − n k = ∑ i = 1 k C k i ∗ n k − i

nk(n1)k=ki=1Cik(n1)ki n k − ( n − 1 ) k = ∑ i = 1 k C k i ∗ ( n − 1 ) k − i

(n1)k(n2)k=ki=1Cik(n2)ki ( n − 1 ) k − ( n − 2 ) k = ∑ i = 1 k C k i ∗ ( n − 2 ) k − i

... . . .

2k1k=ki=1Cik1ki 2 k − 1 k = ∑ i = 1 k C k i ∗ 1 k − i

全都加起来可以得到: (n+1)k1=ki=1CikSum(n,ki) ( n + 1 ) k − 1 = ∑ i = 1 k C k i ∗ S u m ( n , k − i )

(n+1)k+11=k+1i=1Cik+1Sum(n,ki+1) ( n + 1 ) k + 1 − 1 = ∑ i = 1 k + 1 C k + 1 i ∗ S u m ( n , k − i + 1 )

(n+1)k+11=k+1i=2Cik+1Sum(n,ki+1)+(k+1)Sum(n,k) ( n + 1 ) k + 1 − 1 = ∑ i = 2 k + 1 C k + 1 i ∗ S u m ( n , k − i + 1 ) + ( k + 1 ) ∗ S u m ( n , k )

Sum(n,k)=(n+1)k+11k1i=0Cik+1Sum(n,i)k+1 S u m ( n , k ) = ( n + 1 ) k + 1 − 1 − ∑ i = 0 k − 1 C k + 1 i ∗ S u m ( n , i ) k + 1

这样我们就可以 O(k2) O ( k 2 ) 的递推了,临界点是 Sum(n,0)=n S u m ( n , 0 ) = n 记忆化搜索即可

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int mod=1e9+7;
long long n,nw,a[55];
int T,mi,k,ans[55],c[55][55]; 
int fast_pow(int x,int a)
{
    int ans=1;
    for (;a;x=1ll*x*x%mod,a>>=1)
        if (a&1) ans=1ll*ans*x%mod;
    return ans; 
}
int S(int k)
{
    if (k==0) return nw%mod;
    if (ans[k]) return ans[k];
    int Ans=fast_pow((nw+1)%mod,k+1);//cout<<Ans<<endl;
    int jian=1;
    for (int i=0;i<=k-1;i++) jian=(jian+1ll*c[i][k+1]*S(i))%mod;
    Ans=(Ans+mod-jian)%mod;
    Ans=(1ll*Ans*fast_pow(k+1,mod-2))%mod;
    ans[k]=Ans;
    return Ans;
}
int main()
{
    c[0][0]=1;
    for (int i=1;i<=52;i++)
    {
        c[0][i]=1;
        for (int j=1;j<=i;j++) c[j][i]=(1ll*c[j][i-1]+c[j-1][i-1])%mod;
    }
    scanf("%d",&T);
    while (T--)
    {
        scanf("%lld%d",&n,&k);

        for (int i=1;i<=k;i++) scanf("%lld",&a[i]);
        sort(a+1,a+1+k);
        mi=k+1;
        int tot=0;
        nw=n;
        for (int i=1;i<=k;i++)
        {
            memset(ans,0,sizeof(ans));
            tot=(1ll*tot+S(mi))%mod;
            for (int j=i;j<=k;j++) tot=(1ll*tot-fast_pow((a[j]-n+nw)%mod,mi)+mod)%mod;
            //cout<<tot<<endl;
            nw-=a[i]-a[i-1];
        }
        memset(ans,0,sizeof(ans));//cout<<nw<<endl;
        tot=(1ll*tot+S(mi))%mod;
        printf("%d\n",tot);
    }
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值