hdu5829快速数论变换以及任意模数的拓展!!!

写的时候要些变换主要就是抓住1.明白自变量的范围。2.卷积里两个自变量的和是一个常数。3.长度时大于等于实际的二倍的。

还有这题网上大多数的题目推导都是有问题的(导致常数过大,推导是正确的),或者有些小哥的姿势不对,也导致常数过大然后就卡了。。。。尼玛,卡常啊这题。。。。。。分析看下面:

对于任意模数,如果我们要求ans%p,p是任意模数,但是我们只能求mod费马素数的ntt,解决办法就是先算出真正值即还木有模p之前的值,然后再模p这样的话就好了。

先选三个费马素数 m1,m2,m3然后求出ans(modm1*m2*m3)由于ans的最大值都是小于m1*m2*m3的,所以ans%(m1*m2*m3)=ans然后求出来后再ans%p就好了,阔能要高精度

然后就搞吧。

这题的特点就是当你求出了此题的式子以后你会发现变量中的下标是和位置有关的,这时你需要做的就是做变量代换将下标换到和式中

比如此题就是

A[i] = revJ[i];  B[n - i] = ((J[i-1] * pow2[n - i]) % mod)*a[i];   你会发现如果我们让i+k=t 然后 和式就是 A[i]*B[n-t]的形式 i是0-n-k变化的 而且 i+t=n-k是固定的所以现在就变好了注意t当然是大于等于1的,把我下面推导的公式换下就好了,这样写好理解一点

#include<iostream>
#include<cstdio>
#include<functional>
#include<algorithm>
using namespace std;
typedef long long ll;
const long long mod = 998244353;//
const long long g = 3;
long long n;
long long last = 0, llast = 1;
long long pow2[120000], revJ[120000], J[120000], wn[100];
long long A[400500], B[400500], a[400500], f[400500];
void exgcd(long long a, long long b, long long &x, long long &y)
{
	if (b == 0)
	{
		x = 1, y = 0; return;
	}
	else
	{
		exgcd(b, a%b, y, x);
		long long kk1 = (a / b) % mod; long long kk2 = x%mod;
		y -= kk1*kk2;//这不是y-=a/b;
		y = (y%mod + mod) % mod;
	}
}
long long pow(long long value, long long times)
{
	long long ans = 1; long long temp = value;
	for (; times; times = times / 2)
	{
		if (times & 1)ans = (ans*temp) % mod;
		temp = (temp*temp) % mod;
	}
	return ans;
}
void rander(long long *a, long long len)
{
	for (long long i = 1, j = len / 2; i < len - 1; i++)
	{
		if (i < j)
		{
			swap(a[i], a[j]);
		}
		long long N = len / 2;
		while (N <= j)
		{
			j -= N;
			N /= 2;
		}
		j += N;
	}
}

void init(int len)
{
	if (last < len)
		last = len;
	else
		return;
	long long rr, ss;
	for (int i = llast; i <= last; i++)
	{
		exgcd(i, mod, rr, ss);
		revJ[i] = (revJ[i - 1] * rr) % mod;
		pow2[i] = (pow2[i - 1] * 2) % mod;
		J[i] = (J[i - 1] * i) % mod;
	}
}

void ntt(long long *a, long long len, int kind)
{
	rander(a, len); long long change1, change2;
	for (int s = 1; (1 << s) <= len; s++)
	{
		long long m = (1 << s);
		for (int k = 0; k < len; k += m)
		{
			long long w = 1;
			for (int j = k; j < k + m / 2; j++)
			{
				long long left = a[j];
				long long right = (w*a[j + m / 2]) % mod;
				a[j] = (left + right) % mod; a[j + m / 2] = ((left - right) % mod + mod) % mod;
				w = (w*wn[s]) % mod;
			}
		}
	}
	if (kind == -1)
	{
		for (int i = 1; i < len / 2; i++)swap(a[i], a[len - i]);
		long long tempp; long long t = 1;
		exgcd(len, mod, t, tempp);
		for (int i = 0; i < len; i++)
			a[i] = (t*(a[i] % mod)) % mod;
	}
}
int main()
{

	for (int i = 0; i<21; i++)
	{
		int t = 1 << i;
		wn[i] = pow(g, (mod - 1) / t);
	}
	int t;
	scanf("%d", &t);
	revJ[0] = 1; J[0] = 1; pow2[0] = 1;
	while (t--)
	{
		scanf("%d", &n);
		//n = 20;
		for (int i = 1; i <= n; i++)
			scanf("%d", &a[i]);//a[i] = 1000000000;
		sort(a + 1, a + n + 1, greater<ll>());
		init(n);

		for (int i = 0; i < n; i++)
		{
			A[i] = revJ[i];
			//B[n - 1 - i] = (((J[i] * pow2[n - 1 - i]) % mod) * a[i]) % mod;
		}
		for (int i = 1; i <= n; i++)
			B[n - i] = ((J[i-1] * pow2[n - i]) % mod)*a[i];

		long long len = 1;
		while (len / 2 < (n))len *= 2;
		for (int i = n; i < len; i++)A[i] = 0, B[i] = 0;//为什么要这一步?因为在推导fft的时候你会发现我们用的是矩阵来运算的所以不能只考虑w1 w2 ..wn-1而忽略了wn..wlen。
		ntt(A, len, 1);

		ntt(B, len, 1);

		for (int i = 0; i <len; i++)
			A[i] = (A[i] * B[i]) % mod;

		ntt(A, len, -1);
		f[0] = 0;

		for (int i = 1; i <= n; i++)
		{
			f[i] = A[n - i] * revJ[i - 1] % mod;

			f[i] = (f[i] + f[i - 1]) % mod;

			printf("%lld ", f[i]);

		}
		printf("\n");
	}
}


注意B[n-1-i]=...中的A[i]与上面的A[i]不是同一个。。。。为什么这样写网上有很多题解,就是公式推导后常数有点大,改成我这样就好 了!!!!!!

这里推荐一个小哥的,只是他的代码过不了看分析就好http://blog.csdn.net/cqu_hyx/article/details/52194696

//常用费马素数和原根的表
/*是这样的,这几天在写 FFT,由于是在模意义下的,需要各种素数……
然后就打了个表方便以后查了、
如果 r*2^k+1 是个素数,那么在mod r*2^k+1意义下,可以处理 2^k 以内规模的数据,
2281701377=17*2^27+1  是一个挺好的数,平方刚好不会爆 long long
1004535809=479*2^21+1 加起来刚好不会爆 int 也不错
下面是刚刚打出来的表格(gg 是mod(r*2^k+1)的原根)

素数  rr  kk  gg
3   1   1   2
5   1   2   2
17  1   4   3
97  3   5   5
193 3   6   5
257 1   8   3
7681    15  9   17
12289   3   12  11
40961   5   13  3
65537   1   16  3
786433  3   18  10
5767169 11  19  3
7340033 7   20  3
23068673    11  21  3
104857601   25  22  3
167772161   5   25  3
469762049   7   26  3
998244353  119  23  3
1004535809  479 21  3
2013265921  15  27  31
2281701377  17  27  3   //以上素数都是常用的下面的话需要加高精度
3221225473  3   30  5
75161927681 35  31  3
77309411329 9   33  7
206158430209    3   36  22
2061584302081   15  37  7
2748779069441   5   39  3
6597069766657   3   41  5
39582418599937  9   42  5
79164837199873  9   43  5
263882790666241 15  44  7
1231453023109121    35  45  3
1337006139375617    19  46  3
3799912185593857    27  47  5
4222124650659841    15  48  19
7881299347898369    7   50  6
31525197391593473   7   52  3
180143985094819841  5   55  6
1945555039024054273 27  56  5
4179340454199820289 29  57  3

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值