BZOJ 5339 教科书般的亵渎(组合数学)

探讨了给定怪物血量及特定亵渎卡效果下,如何高效计算杀死所有怪物的总分数。采用升序序列和第二类斯特林数的方法,通过预处理解决了自然数幂和的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

nmn−m只怪物,血量分别为11~n中去掉mm个数字a1,...,am,一次亵渎卡的效果是:首先所有怪物血量减一,如果存在怪物血量清零则所有怪物血量继续减一,使用该亵渎卡后,每只怪物对分数的贡献为xkxk,其中xx是该怪物在使用该卡之前的血量,k为杀死所有怪物所需的亵渎卡数量,问杀死所有怪物所得分数和

Input

第一行一整数TT表示用例组数,每组用例首先输入两整数n,m,之后输入mm个整数a1,...,am

(1T10,1n1013,1m50,1ain,aiaj)(1≤T≤10,1≤n≤1013,1≤m≤50,1≤ai≤n,ai≠aj)

Output

输出得分,结果模109+7109+7

Sample Input

2
10 1
5
4 2
1
2

Sample Output

415
135

Solution

假设aa序列升序且a0=0,那么第一张亵渎卡的效果会杀掉[1,a11][1,a1−1]的所有怪物,而之后的怪物血量变成[1,a2a11],[a2a1+1,a3a11],...,[ama1+1,na1][1,a2−a1−1],[a2−a1+1,a3−a1−1],...,[am−a1+1,n−a1],显然每使用一次亵渎就会消去一个区间,故k=m+1k=m+1,且第ii次使用亵渎得分为i=1nai1im+1j=im(ajai1)m+1,故问题转化为求自然数幂和,用伯努利数或第二类斯特林数均可求出幂和,此处说一下如何用第二类斯特林数求解,记Sk(n)=i=1nikSk(n)=∑i=1nik

考虑恒等式nk=i=1kAinS(k,i)nk=∑i=1kAni⋅S(k,i),我们有

Sk(n)=i=0nj=1kAjiS(k,j)=j=1kS(k,j)i=0nAjiSk(n)=∑i=0n∑j=1kAij⋅S(k,j)=∑j=1kS(k,j)∑i=0nAij

注意到
i=0nAji=j!i=0nCji=j!Cj+1n+1=1j+1Cj+1n+1∑i=0nAij=j!∑i=0nCij=j!Cn+1j+1=1j+1Cn+1j+1

故有
Sk(n)=i=1kS(k,i)Ci+1n+1i+1Sk(n)=∑i=1kS(k,i)⋅Cn+1i+1i+1

连续i+1i+1个数字相乘必可被i+1i+1整除,故对任意模数均可O(k)O(k)计算Ci+1n+1i+1Cn+1i+1i+1,预处理第二类斯特林数和单次查询时间复杂度均为O(k2)O(k2)

Code

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
#define maxn 105
#define mod 1000000007
int mul(int x,int y)
{
    ll z=1ll*x*y;
    return z-z/mod*mod;
}
int add(int x,int y)
{
    x+=y;
    if(x>=mod)x-=mod;
    return x;
}
int Pow(int x,int y)
{
    int ans=1;
    while(y)
    {
        if(y&1)ans=mul(ans,x);
        x=mul(x,x);
        y>>=1;
    }
    return ans;
}
int S[maxn][maxn];
void init(int n=52)
{
    S[0][0]=1;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=i;j++)
            S[i][j]=add(S[i-1][j-1],mul(j,S[i-1][j]));
}
int Solve(ll n,int k)
{
    int ans=0;
    for(int i=1;i<=k;i++)
    {
        int res=S[k][i];
        for(ll j=n+1-i;j<=n+1;j++)
            if(j%(i+1)==0)res=mul(res,j/(i+1)%mod);
            else res=mul(res,j%mod);
        ans=add(ans,res);
    }
    return ans;
}
int T,m;
ll n,a[maxn];
int main()
{
    init();
    scanf("%d",&T);
    while(T--)
    {
        scanf("%lld%d",&n,&m);
        for(int i=1;i<=m;i++)scanf("%lld",&a[i]);
        sort(a+1,a+m+1);
        int ans=0;
        for(int i=1;i<=m+1;i++)
        {
            ans=add(ans,Solve(n-a[i-1],m+1));
            for(int j=i;j<=m;j++)ans=add(ans,mod-Pow((a[j]-a[i-1])%mod,m+1));
        }
        printf("%d\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值