[BZOJ 5339] 教科书般的亵渎

本文介绍了如何解决BZOJ 5339题目,通过观察得出k=m+1的规律,然后对怪物血量进行排序,并利用拉格朗日插值法计算怪物血量的k次方之和。通过取k+1个点并计算对应f(n),结合费马小定理求逆元,实现了O(k^3)的时间复杂度解决方案。

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

题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=5339
题面在这里插入图片描述
思路
观察一下可知 k = m + 1 k=m+1 k=m+1
a i a_i ai从小到大排个序,再加上一个 a 0 = 0 a_0=0 a0=0那么我们需要计算 m + 1 m+1 m+1次,每次计算仍在场上的怪物血量的k次方之和,那么最终答案就是 ∑ i = 0 m ( ∑ j = a i + 1 n − a i ( j − a i ) k − ∑ j = i + 1 m ( a j − a i ) k ) \sum_{i=0}^m(\sum_{j={a_i+1}}^{n-a_i}(j-a_i)^k-\sum_{j=i+1}^m(a_j-a_i)^k) i=0m(j=ai+1nai(jai)kj=i+1m(ajai)k)再取个模
由拉格朗日插值法(这里百度百科
我们令 f ( n ) = ∑ j = 1 n j k f(n)=\sum_{j={1}}^{n}j^k f(n)=j=1njk那么我们只需取 k + 1 k+1 k+1个点 x 0 = 0 , x 1 = 1 … … x k = k x_0=0,x_1=1……x_k=k x0=0,x1=1xk=k分别计算出 f ( 1 ) , f ( 2 ) … … f ( k ) f(1),f(2)……f(k) f(1),f(2)f(k)。那么就有 f ( n ) = ( n − x 1 ) ( n − x 2 ) … … ( n − x k ) ( x 0 − x 1 ) ( x 0 − x 2 ) … … ( x 0 − x k ) y 0 + ( n − x 0 ) ( n − x 2 ) … … ( n − x k ) ( x 1 − x 0 ) ( x 1 − x 2 ) … … ( x 1 − x k ) y 1 + … … ( n − x 0 ) ( n − x 1 ) … … ( n − x k − 1 ) ( x k − x 0 ) ( x k − x 1 ) … … ( x k − x k − 1 ) y k f(n)=\frac{(n-x_1)(n-x_2)……(n-x_k)}{(x_0-x_1)(x_0-x_2)……(x_0-x_k)}y_0+\frac{(n-x_0)(n-x_2)……(n-x_k)}{(x_1-x_0)(x_1-x_2)……(x_1-x_k)}y_1+……\frac{(n-x_0)(n-x_1)……(n-x_{k-1})}{(x_k-x_0)(x_k-x_1)……(x_k-x_{k-1})}y_k f(n)=(x0x1)(x0x2)(x0xk)(nx1)(nx2)(nxk)y0+(x1x0)(x1x2)(x1xk)(nx0)(nx2)(nxk)y1+(xkx0)(xkx1)(xkxk1)(nx0)(nx1)(nxk1)yk考虑到除法不太好算,我们需要对分母求逆元(费马小定理相关)if(sum>0)x[i]=fast_pow(sum,MOD-2);else x[i]=-fast_pow(-sum,MOD-2);

然后暴力算(对于后面需要减去的部分暴力枚举算)即可。单次计算的时间复杂度是 O ( k 2 ) O(k^2) Ok2
总计时间复杂度 O ( k 3 ) O(k^3) O(k3)

AC代码

#include<stdio.h>
#include<map>
#include<queue>
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#include<stdlib.h>
#include<string.h>
#define MOD 1000000007
typedef long long LL;
using namespace std;
int t;
LL n,m,ans;
LL cz[100];
LL x[100],y[100];
long long fast_pow(long long target,long long p);
void pre()
{
 LL te=m+2;
 LL sum=0;
 for(LL i=1;i<=te;i++)
 {
  sum+=fast_pow(i,m+1);
  sum%=MOD;
  y[i]=sum;
 }
 for(LL i=1;i<=te;i++)
 {
  sum=1;
  for(LL j=0;j<=te;j++) if(j!=i)
  {
   sum*=(i-j);
   if(sum>MOD)sum%=MOD;
   else if(-sum>MOD)sum=-(-sum%MOD);
  }
  if(sum>0)x[i]=fast_pow(sum,MOD-2);else x[i]=-fast_pow(-sum,MOD-2);//记录每个分母的逆元
 }
}
LL calc(LL now)
{
 LL te=m+2;
 LL sum,cot=0;
 for(LL i=1;i<=te;i++)
 {
  sum=1;
  for(LL j=0;j<=te;j++)
  if(j!=i)
  {
   sum*=(n-cz[now]-j);sum%=MOD;
  }
  sum*=y[i];sum%=MOD;
  sum*=x[i];
  if(sum>0)sum%=MOD;
  else sum=-(-sum%MOD);
  cot+=sum;
 }
 for(LL i=now+1;i<=m;i++)cot-=fast_pow(cz[i]-cz[now] ,m+1);
 if(cot<0)cot+=(((-cot)/MOD+1)*MOD);
 cot%=MOD;
 return cot;
}
int main()
{
 scanf("%d",&t);
 while(t--)
 {
  ans=0;
  scanf("%lld%lld",&n,&m);
  for(LL i=1;i<=m;i++) scanf("%lld",&cz[i]);
  sort(cz+1,cz+m+1);
  pre();//预处理求每个对应的y以及每个分母的逆元(显然分母不变)
  for(LL i=0;i<=m;i++)
  ans+=calc(i);//计算每次的值,累加
  ans%=MOD; 
  printf("%lld\n",ans);
 }
 return 0;
}
long long fast_pow(long long target,long long p)
{
 long long a[50];
 a[1]=target;
 for(int i=2;i<50;i++)
 {
  a[i]=a[i-1]*a[i-1];a[i]%=MOD;
 }
 long long ans=1;
 for(int i=1;p;i++)
 {
  if(p%2) ans=(ans*a[i])%MOD;p/=2;
 }
 return ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值