【P4593 [TJOI2018]】教科书般的亵渎(拉格朗日插值)

题目描述

小豆喜欢玩游戏,现在他在玩一个游戏遇到这样的场面,每个怪的血量为ai​,且每个怪物血量均不相同,小豆手里有无限张“亵渎”。亵渎的效果是对所有的怪造成1点伤害,如果有怪死亡,则再次施放该法术。我们认为血量为0怪物死亡。

小豆使用一张 “亵渎”会获得一定的分数,分数计算如下,在使用一张“亵渎”之后,每一个被亵渎造成伤害的怪会产生x^k,其中x是造成伤害前怪的血量为x和需要杀死所有怪物所需的“亵渎”的张数k。

输入输出格式

输入格式:

第一行输入一个T(T≤10),表示有多少组测试数据

每组组测试数据第一行为n,m,表示有当前怪物最高的血量n,和m种没有出现的血量

接下来m行,每行1个数ai​,表示场上没有血量为ai​的怪物

输出格式:

一共TT行,每行一个数, 第ii行表示第ii组测试数据中小豆的最后可以获得的分数, 因为这个分数会很大需要模10^9+7109+7

输入输出样例

输入样例#1: 复制

2
10 1
5
4 2
1
2

输出样例#1: 复制

415
135

说明

对于10%的数据,有m=0

对于20%的数据,有m≤1

对于30%的数据,有m≤2

对于40%的数据,有m≤3

对于50%的数据,有m≤4

对于60%的数据,有m≤5

对于100%的数据,有m≤50

对于100%的数据,有n≤10^13。

思路:

因为所有数是连续的,若m=0时,只要用一次亵渎就可以使所有怪物死亡,否则的话就需要用m+1张亵渎。因为缺少m种血量的怪物,所以怪物按照血量可以被分成几段,因此具体的操作,就是用1次亵渎后,所有怪物的血量都要减少,同时这里产生了一次积分,这里的积分可以分段来求,对于每一段,求出从1到段结尾的自然数幂和减去1到段开始的自然数幂和,就是这段的贡献的分数。然后重复m+1次操作直到将所有的怪物都死亡。因此这里的关键是求前n项的自然数幂和,这里采用拉格朗日插值来求。

参考博客:(博客1博客2

ac代码:

// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn = 66,mod = 1e9+7;
ll fp(ll a,ll b){//快速幂  fastpow
    ll res=1;
    while(b){
        if(b&1){
            res=res*a%mod;
        }
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
//ll inv[3700];
ll a[maxn];
ll y[maxn],fac[maxn],ifac[maxn],pre[maxn],suf[maxn];
/*
拉格朗日插值求自然数幂和,自然数幂和是以n为自变量的k+1阶多项式,但是多项式的具体形式我们不知道,
但是我们可以通过代入k+1个已知点,来进行插值,插值出来的结果就是自然数幂和的结果 
*/ 
ll get(ll n,ll m){ //自然数幂和 
    ll lim=m+1,ans=0;//m+1   ->   i^m次方对应m+1阶多项式 
    memset(y,0,sizeof(y));
    for(int i=1;i<=lim;i++) y[i]=(y[i-1]+fp(i,m))%mod;//找k+1个已知点,即求前k个自然数幂和
    pre[0] = n; suf[lim+1] = 1;
    for(int i = 1;i <= lim;i++) pre[i] = 1ll*pre[i-1]*(n-i)%mod;
    for(int i=lim;i >= 1;i--) suf[i]=1ll*suf[i+1]*(n-i)%mod;
    //连乘的时候要求i!=j的,所以乘积中是没有i这个数的,因此要求前缀积和后缀积,然后通过两者相乘,求出不含i的乘积,这里不用除法是因为取余的原因吗,否则就需要取逆元 
    for(int i = 0;i <= lim;i++){
        ll up = 1ll*y[i]*(pre[i-1]*suf[i+1]%mod)%mod;
        ll down = 1ll*ifac[i]*ifac[lim-i]%mod;
        if((lim-i) & 1) down = mod-down;//lim-i为奇数fac[lim-i]应该为负,即down应该为-down,所以这里需要加上mod 
        ans = (ans+1ll*up*down%mod)%mod;
    }
    return ans;
}
void solve(){
    ll n,m;
    scanf("%lld%lld",&n,&m);
    memset(a,0,sizeof(a));
    ll ans=0;
    for(ll i = 1;i<=m;i++) scanf("%lld",&a[i]);
    a[++m] = ++n;//要计算缺少点之前的自然数幂和,所以所有数之后还要加一个点 
    sort(a+1,a+m+1);
    for(ll i = 1;i<=m;i++){
        for(ll j = i;j <= m;j++) 
        ans=(ans+(get(a[j] - 1, m )-get(a[j - 1], m)+mod)%mod)%mod;
        for(ll j = i+1;j <= m;j++)
        a[j]=a[j]-a[i];
        a[i]=0;
    } 
    printf("%lld\n",ans);
}
int main()
{
//	inv[1] = 1;
//	for(int i = 2;i<=3600;i++)
//	inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;//逆元 
    fac[0]=1;
    for(int i = 1;i<=60;i++)
    fac[i]=1ll*i*fac[i-1]%mod;//阶乘
    ifac[60]=fp(fac[60],mod-2);
    for(int i=60;i>=1;i--)
    ifac[i-1] = 1ll*ifac[i]*i%mod;//应该是求阶乘的逆元 
    int t;
    cin>>t;
    while(t--){
        solve();
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值