luogu P4593 [TJOI2018]教科书般的亵渎

本文探讨了一道关于怪物血量和伤害计算的算法题,通过伯努利数和拉格朗日差值两种方法求解。文章详细解析了伯努利数在多项式求和中的应用,以及拉格朗日差值在高次多项式插值中的高效计算。代码实现展示了两种方法的具体操作。

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

背景:

出题人的语文跟体育老师学的吧(别喷我,我真看不懂,还没有样例解释)?
伯努利数是一个好东西。

题目传送门:

https://www.luogu.org/problemnew/show/P4593

题意:

n − m n-m nm只怪物,其血量为 [ 1 , n ] [1,n] [1,n],其中 m m m表示有 m m m种血量未出现,给出未出现的血量 a i , i ∈ [ 1 , m ] a_i,i∈[1,m] ai,i[1,m],现在求将所有怪物杀死的贡献。
怎么杀怪物?每一次你可以用一张“亵渎”(据说是炉石传说里的?),对所有怪造成一点伤害,若有怪死亡,则继续生效;否则再用一张新的亵渎,直到所有怪死亡。其贡献为 ∑ o p = 1 k ∑ i = 1 n x i k \sum_{op=1}^{k}\sum_{i=1}^{n}x_i^k op=1ki=1nxik k k k为杀死所有怪的用的亵渎的张数, x i x_i xi为在使用第 o p op op张亵渎前编号为 i i i的怪的血量。

思路 1 1 1

容易发现 k = m + 1 k=m+1 k=m+1
问题就转成了求 ∑ o p = 1 m + 1 ∑ i = 1 n x i m + 1 \sum_{op=1}^{m+1}\sum_{i=1}^{n}x_i^{m+1} op=1m+1i=1nxim+1

发现 m m m非常小,一段连续的血量的怪的起始和结束血量可以很容易算出来。
那么我们怎么求一个形如 ∑ i = 1 n i k \sum_{i=1}^{n}i^{k} i=1nik的式子。
当然用伯努利数啊。
∑ i = 1 n i k = 1 k + 1 ∑ i = 1 k + 1 C k + 1 i ⋅ B k + 1 − i ⋅ ( n + 1 ) i \sum_{i=1}^{n}i^{k}=\frac{1}{k+1}\sum_{i=1}^{k+1}C_{k+1}^{i}\cdot B_{k+1-i}\cdot (n+1)^i i=1nik=k+11i=1k+1Ck+1iBk+1i(n+1)i

输入的不存在的血量没有排序啊,坑了我好久。

代码 1 1 1

#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define mod 1000000007
using namespace std;
	LL n,ans;
	int k;
	LL fac[100],inv[100],Inv[100],d[100],B[100];
	struct node{LL x,y;} a[100];
LL calc_C(LL n,LL m)
{
	if(m>n) return 0;
	return n<mod?fac[n]*Inv[n-m]%mod*Inv[m]%mod:calc_C(n/mod,m/mod)*calc_C(n%mod,m%mod)%mod;
}
void init(LL ma)
{
	fac[0]=fac[1]=inv[0]=inv[1]=Inv[0]=Inv[1]=1;
	for(int i=2;i<=ma;i++)
		fac[i]=fac[i-1]*i%mod;
	for(int i=2;i<=ma+1;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
	for(int i=2;i<=ma;i++) Inv[i]=Inv[i-1]*inv[i]%mod;

	B[0]=1;
	for(int i=1;i<=ma;i++)
	{
		for(int j=0;j<i;j++)
			B[i]=(B[i]+B[j]*calc_C(i+1,j)%mod)%mod;
		B[i]=(-inv[i+1]*B[i]%mod+mod)%mod;
	}
}
LL calc(LL x)
{
	LL op=1,sum=0;
	x%=mod;
	for(int i=1;i<=k+1;i++)
	{
		op=op*(x+1)%mod;
		sum=(sum+calc_C(k+1,i)*B[k+1-i]%mod*op%mod)%mod;
	}
	return sum*inv[k+1]%mod;
}
int main()
{
	int T;
	init(60);
	scanf("%d",&T);
	while(T--)
	{
		scanf("%lld %d",&n,&k);
		for(int i=1;i<=k;i++)
			scanf("%lld",&d[i]);
		sort(d+1,d+k+1);
		for(int i=1;i<=k;i++)
			a[i]=(node){d[i-1]+1,d[i]-1};
		a[k+1]=(node){d[k]+1,n};
		k++;
		ans=0;
		for(int i=1;i<=k;i++)
		{
			if(a[i].x>a[i].y) continue;
//			for(int j=1;j<=k;j++)
//			printf("%lld %lld\n",a[j].x,a[j].y);
//			printf("\n");
			while(a[i].x>0)
			{
				for(int j=i;j<=k;j++)
				{
					if(a[i].x>a[i].y) continue;
					ans=(ans+(calc(a[j].y)-calc(a[j].x-1)+mod)%mod)%mod;
//					printf("%lld %lld %lld %lld\n",a[j].x-1,calc(a[j].x-1),a[j].y,calc(a[j].y));
					a[j].x--,a[j].y--;
				}
			}
			for(int j=i+1;j<=k;j++)
				a[j].x-=a[i].y+1,a[j].y-=a[i].y+1;
//			for(int j=1;j<=k;j++)
//				printf("%lld %lld\n",a[j].x,a[j].y);
//			printf("\n");
		}
		printf("%lld\n",ans);
	}
}


思路 2 2 2

拉格朗日差值。
形如 ∑ i = 1 n i k \sum_{i=1}^{n}i^{k} i=1nik的式子的次数为 k + 1 k+1 k+1,因此插入前 k + 2 k+2 k+2个的前缀和即可。

代码 2 2 2

跑得巨慢(当然你可以重心拉格朗日差值,我就不打了吧)。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define mod 1000000007
using namespace std;
	LL n,ans;
	int k;
	LL d[60];
	struct node{LL x,y;} a[100],b[100];
LL dg(LL x,LL k)
{
	if(!k) return 1;
	LL op=dg(x,k>>1);
	if(k&1) return op*op%mod*x%mod; else return op*op%mod;
}
LL inv(LL x)
{
	return dg(x,mod-2);
}
LL lagrange(LL x)
{
	LL sum=0;
	x%=mod;
	for(int i=0;i<=k+1;i++)
	{
		LL op1=b[i].y,op2=1;
		for(int j=0;j<=k+1;j++)
		{
			if(i==j) continue;
			op1=op1*((x-b[j].x+mod)%mod)%mod;
			op2=op2*((b[i].x-b[j].x+mod)%mod)%mod;
		}
		sum=(sum+op1*inv(op2)%mod)%mod;
	}
	return sum;
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%lld %d",&n,&k);
		LL sum=0;
		for(int i=0;i<=k+2;i++)
		{
			sum=(sum+dg(i,k+1))%mod;
			b[i]=(node){i,sum};
		}
		for(int i=1;i<=k;i++)
			scanf("%lld",&d[i]);
		sort(d+1,d+k+1);
		for(int i=1;i<=k;i++)
			a[i]=(node){d[i-1]+1,d[i]-1};
		a[k+1]=(node){d[k]+1,n};
		k++;
		ans=0;
		for(int i=1;i<=k;i++)
		{
			if(a[i].x>a[i].y) continue;
//			for(int j=1;j<=k;j++)
//			printf("%lld %lld\n",a[j].x,a[j].y);
//			printf("\n");
			while(a[i].x>0)
			{
				for(int j=i;j<=k;j++)
				{
					if(a[i].x>a[i].y) continue;
					ans=(ans+(lagrange(a[j].y)-lagrange(a[j].x-1)+mod)%mod)%mod;
//					printf("%lld %lld %lld %lld\n",a[j].x-1,calc(a[j].x-1),a[j].y,calc(a[j].y));
					a[j].x--,a[j].y--;
				}
			}
			for(int j=i+1;j<=k;j++)
				a[j].x-=a[i].y+1,a[j].y-=a[i].y+1;
//			for(int j=1;j<=k;j++)
//				printf("%lld %lld\n",a[j].x,a[j].y);
//			printf("\n");
		}
		printf("%lld\n",ans);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值