洛谷P2000 拯救世界(NTT+生成函数)

这篇博客介绍了如何利用NTT(快速傅里叶变换)和生成函数来解决洛谷P2000题目中关于不同神石组合的问题。通过金、木、水、火、土五种神石的限制条件,推导出组合公式,并应用广义二项式定理简化计算,最后给出高精度计算的代码实现。

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

题目链接
这题还是蛮不错的,去年noip之前就推出了通项,但因为FFT被卡了精度就一直没写
和那道食物差不多的推式子方法
kkksc03大神召唤方法:
金神石的块数必须是6的倍数。
1 + x 6 + x 12 + . . . = 1 x 6 − 1 1+x^6+x^{12}+...=\frac{1}{x^6-1} 1+x6+x12+...=x611
木神石最多用9块。
1 + x + x 2 + . . . + x 9 = x 10 − 1 x − 1 1+x+x^2+...+x^9=\frac{x^{10}-1}{x-1} 1+x+x2+...+x9=x1x101
水神石最多用5块。
x 6 − 1 x − 1 \frac{x^6-1}{x-1} x1x61
火神石的块数必须是4的倍数。
1 x 4 − 1 \frac{1}{x^4-1} x411
土神石最多用7块。
x 8 − 1 x − 1 \frac{x^8-1}{x-1} x1x81
lzn大神召唤方法:
金神石的块数必须是2的倍数。
1 x 2 − 1 \frac{1}{x^2-1} x211
木神石最多用1块。
x 2 − 1 x − 1 \frac{x^2-1}{x-1} x1x21
水神石的块数必须是8的倍数。
1 x 8 − 1 \frac{1}{x^8-1} x811
火神石的块数必须是10的倍数。
1 x 10 − 1 \frac{1}{x^{10}-1} x1011
土神石最多用3块。
x 4 − 1 x − 1 \frac{x^4-1}{x-1} x1x41

反正一乘就会发现消掉了一堆,得到了
1 ( 1 − x ) 5 \frac{1}{(1-x)^5} (1x)51

广义二项式定理一下就是 c n + 5 − 1 5 − 1 = ( n + 1 ) ( n + 2 ) ( n + 3 ) ( n + 4 ) 24 c^{5-1}_{n+5-1}=\frac{(n+1)(n+2)(n+3)(n+4)}{24} cn+5151=24(n+1)(n+2)(n+3)(n+4)

因为长度很长所以要高精度,用NTT搞下就可以了

代码如下:(一如既往的难看

#include<bits/stdc++.h>
#define mod 998244353
#define g 3ll
using namespace std;

char s[100010];

long long kasumi(long long a,long long b)
{
	long long ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}

long long n1[600040],n2[600040],n3[600040],n4[600040],ans[600040];
long long len1,len2,len3,len4;
long long limit=1,r[600040];

long long NTT(long long *a,long long kd)
{
	for(long long i=0;i<limit;i++)
	{
		if(i<r[i])
		{
			swap(a[i],a[r[i]]);
		}
	}
	for(long long mid=1;mid<limit;mid<<=1)
	{
		long long tmp=kasumi(g,(mod-1)/(mid*2));
		if(kd) tmp=kasumi(tmp,mod-2);
		for(long long i=0;i<limit;i+=mid*2)
		{
			long long w=1;
			for(long long j=0;j<mid;j++,w=w*tmp%mod)
			{
				long long x=a[i+j];
				long long y=w*a[i+j+mid]%mod;
				a[i+j]=(x+y+mod)%mod;
				a[i+j+mid]=(x-y+mod)%mod;
			}
			// cerr<<w<<endl;
		}
	}
	if(kd)
	{
		long long inv=kasumi(limit,mod-2);
		for(long long i=0;i<limit;i++)
		{
			a[i]=a[i]*inv%mod;
		}
	}
}

void mul(long long *a,long long *b)
{
	NTT(a,0);
	NTT(b,0);
	for(long long i=0;i<limit;i++)
	{
		a[i]=a[i]*b[i]%mod;
	}
	NTT(a,1);
	long long tmp=0;
	for(long long i=0;i<limit;i++)
	{
		n1[i]+=tmp;
		tmp=n1[i]/10;
		n1[i]%=10;
		if(n1[i]) len1=i+1;
	}
}

int main()
{
	long long ll=0;
	scanf("%s",s);
	long long len=strlen(s);
	while(limit<=400000) limit<<=1,ll++;
	for(int i=0;i<limit;i++)
	{
		r[i]=r[i>>1]>>1|((i&1)<<(ll-1));
	}
	reverse(s,s+len);
	for(int i=0;i<len;i++)
	{
		n1[i]=s[i]-'0';
	}
	long long x=1;
	for(int i=0;i<=len;i++)
	{
		n1[i]=n1[i]+x;
		x=n1[i]/10;
		n1[i]=n1[i]%10;
		if(n1[i]) len1=i+1;
	}
	x=1;
	for(int i=0;i<len1;i++)
	{
		n2[i]=n1[i]+x;
		x=n2[i]/10;
		n2[i]=n2[i]%10;
		if(n2[i]) len2=i+1;
	}
	x=1;
	for(int i=0;i<len2;i++)
	{
		n3[i]=n2[i]+x;
		x=n3[i]/10;
		n3[i]=n3[i]%10;
		if(n3[i]) len3=i+1;
	}
	x=1;
	for(int i=0;i<len3;i++)
	{
		n4[i]=n3[i]+x;
		x=n4[i]/10;
		n4[i]=n4[i]%10;
		if(n4[i]) len4=i+1;
	}
	mul(n1,n2);
	mul(n1,n3);
	mul(n1,n4);
	long long p=0,lenn=0;
	for(int i=len1-1;i>=0;i--)
	{
		p=p*10+n1[i];
		ans[i]=p/24;
		p%=24;
		if(ans[i]&&!lenn) lenn=i+1;
	}
	if(!lenn) lenn=1;
	for(int i=lenn-1;i>=0;i--)
	{
		printf("%lld",ans[i]);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值