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

Description

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

Input

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

(1T10,1n1013,1m50,1ain,aiaj) ( 1 ≤ T ≤ 10 , 1 ≤ n ≤ 10 13 , 1 ≤ m ≤ 50 , 1 ≤ a i ≤ n , a i ≠ a j )

Output

输出得分,结果模 109+7 10 9 + 7

Sample Input

2
10 1
5
4 2
1
2

Sample Output

415
135

Solution

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

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

Sk(n)=i=0nj=1kAjiS(k,j)=j=1kS(k,j)i=0nAji S k ( n ) = ∑ i = 0 n ∑ j = 1 k A i j ⋅ S ( k , j ) = ∑ j = 1 k S ( k , j ) ∑ i = 0 n A i j

注意到
i=0nAji=j!i=0nCji=j!Cj+1n+1=1j+1Cj+1n+1 ∑ i = 0 n A i j = j ! ∑ i = 0 n C i j = j ! C n + 1 j + 1 = 1 j + 1 C n + 1 j + 1

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

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

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值