• 【知识总结】多项式全家桶(四)(快速幂和开根)


    上一篇:【知识总结】多项式全家桶(三)(任意模数NTT)

    推荐小恐龙的博客(参考资料):多项式开根

    (本文中一切多项式运算默认在模 (x_n) 意义下进行)

    一、快速幂

    多项式快速幂?首先有一种很显然的方式是把整数快速幂里面的整数乘法替换成多项式乘法 NTT ,复杂度 (O(nlog^2n))

    然而还有一种 (O(nlog n)) 的做法:要求 (B=A^k) ,相当于求 (log_A B=k) ,用换底公式得 (log_A B=frac{ln B}{ln A}=k) ,所以 (B=e^{kln A})

    然后写个多项式对数函数和指数函数(参见【知识总结】多项式全家桶(二)(ln和exp) )就完了。

    注意,多项式运算是对 (x^n) 取模而不是对 (998244353) 取模!对质数取模仅仅是为了避免出现高精度或小数,对多项式运算没有任何影响。所以不要像我一样傻以为指数上要对 (998244352) 取模 QAQ 。

    代码:

    #include <cstdio>
    #include <cctype>
    #include <cstring>
    #include <algorithm>
    #include <climits>
    using namespace std;
    
    namespace zyt
    {
    	template<typename T>
    	inline bool read(T &x, const int p = INT_MAX)
    	{
    		char c;
    		bool f = false;
    		x = 0;
    		do
    			c = getchar();
    		while (c != EOF && c != '-' && !isdigit(c));
    		if (c == EOF)
    			return false;
    		if (c == '-')
    			f = true, c = getchar();
    		do
    			x = (x * 10LL + c - '0') % p, c = getchar();
    		while (isdigit(c));
    		if (f)
    			x = -x;
    		return true;
    	}
    	template<typename T>
    	inline void write(T x)
    	{
    		static char buf[20];
    		char *pos = buf;
    		if (x < 0)
    			putchar('-'), x = -x;
    		do
    			*pos++ = x % 10 + '0';
    		while (x /= 10);
    		while (pos > buf)
    			putchar(*--pos);
    	}
    	typedef long long ll;
    	const int N = 1e5 + 10, p = 998244353, g = 3;
    	inline int power(int a, int b)
    	{
    		int ans = 1;
    		while (b)
    		{
    			if (b & 1)
    				ans = (ll)ans * a % p;
    			a = (ll)a * a % p;
    			b >>= 1;
    		}
    		return ans;
    	}
    	inline int get_inv(const int a)
    	{
    		return power(a, p - 2);
    	}
    	namespace Polynomial
    	{
    		const int LEN = N << 2;
    		int omega[LEN], winv[LEN], rev[LEN];
    		void init(const int n, const int lg2)
    		{
    			int w = power(g, (p - 1) / n), wi = get_inv(w);
    			omega[0] = winv[0] = 1;
    			for (int i = 1; i < n; i++)
    			{
    				omega[i] = (ll)omega[i - 1] * w % p;
    				winv[i] = (ll)winv[i - 1] * wi % p;
    			}
    			for (int i = 0; i < n; i++)
    				rev[i] = ((rev[i >> 1] >> 1) | ((i & 1) << (lg2 - 1)));
    		}
    		void ntt(int *a, const int *w, const int n)
    		{
    			for (int i = 0; i < n; i++)
    				if (i < rev[i])
    					swap(a[i], a[rev[i]]);
    			for (int l = 1; l < n; l <<= 1)
    				for (int i = 0; i < n; i += (l << 1))
    					for (int k = 0; k < l; k++)
    					{
    						int x = a[i + k], y = (ll)a[i + l + k] * w[n / (l << 1) * k] % p;
    						a[i + k] = (x + y) % p;
    						a[i + l + k] = (x - y + p) % p;
    					}
    		}
    		void mul(const int *a, const int *b, int *c, const int n)
    		{
    			static int x[LEN], y[LEN];
    			int m = 1, lg2 = 0;
    			while (m < (n << 1) - 1)
    				m <<= 1, ++lg2;
    			init(m, lg2);
    			memcpy(x, a, sizeof(int[n]));
    			memset(x + n, 0, sizeof(int[m - n]));
    			memcpy(y, b, sizeof(int[n]));
    			memset(y + n, 0, sizeof(int[m - n]));
    			ntt(x, omega, m), ntt(y, omega, m);
    			for (int i = 0; i < m; i++)
    				x[i] = (ll)x[i] * y[i] % p;
    			ntt(x, winv, m);
    			int invm = get_inv(m);
    			for (int i = 0; i < n; i++)
    				c[i] = (ll)x[i] * invm % p;
    		}
    		void _inv(const int *A, int *B, const int n)
    		{
    			if (n == 1)
    				B[0] = 1;
    			else
    			{
    				static int tmp[LEN];
    				_inv(A, B, (n + 1) >> 1);
    				int m = 1, lg2 = 0;
    				while (m < (n << 1) - 1)
    					m <<= 1, ++lg2;
    				init(m, lg2);
    				memcpy(tmp, A, sizeof(int[n]));
    				memset(tmp + n, 0, sizeof(int[m - n]));
    				memset(B + ((n + 1) >> 1), 0, sizeof(int[m - ((n + 1) >> 1)]));
    				ntt(tmp, omega, m), ntt(B, omega, m);
    				for (int i = 0; i < m; i++)
    					B[i] = (ll)(B[i] * 2LL % p - (ll)tmp[i] * B[i] % p * B[i] % p + p) % p;
    				ntt(B, winv, m);
    				int invm = get_inv(m);
    				for (int i = 0; i < n; i++)
    					B[i] = (ll)B[i] * invm % p;
    				memset(B + n, 0, sizeof(int[m - n]));
    			}
    		}
    		void inv(const int *A, int *B, const int n)
    		{
    			static int x[LEN];
    			memcpy(x, A, sizeof(int[n]));
    			_inv(x, B, n);
    		}
    		void derivative(const int *A, int *B, const int n)
    		{
    			for (int i = 1; i < n; i++)
    				B[i - 1] = (ll)A[i] * i % p;
    			B[n - 1] = 0;
    		}
    		void integrate(const int *A, int *B, const int n)
    		{
    			for (int i = n - 1; i >= 0; i--)
    				B[i + 1] = (ll)A[i] * get_inv(i + 1) % p;
    			B[0] = 0;
    		}
    		void ln(const int *A, int *B, const int n)
    		{
    			static int tmp1[LEN], tmp2[LEN];
    			derivative(A, tmp1, n);
    			inv(A, tmp2, n - 1);
    			mul(tmp1, tmp2, B, n - 1);
    			integrate(B, B, n - 1);
    		}
    		void _exp(const int *A, int *B, const int n)
    		{
    			if (n == 1)
    				B[0] = 1;
    			else
    			{
    				static int tmp[LEN];
    				_exp(A, B, (n + 1) >> 1);
    				ln(B, tmp, n);
    				for (int i = 0; i < n; i++)
    					tmp[i] = (-tmp[i] + A[i] + p) % p;
    				tmp[0] = (tmp[0] + 1) % p;
    				mul(B, tmp, B, n);
    			}
    		}
    		void exp(const int *a, int *b, const int n)
    		{
    			static int tmp[LEN];
    			memcpy(tmp, a, sizeof(int[n]));
    			_exp(tmp, b, n);
    		}
    	}
    	int work()
    	{
    		using namespace Polynomial;
    		static int a[LEN];
    		int n, k;
    		read(n), read(k, p);
    		for (int i = 0; i < n; i++)
    			read(a[i], p);
    		ln(a, a, n);
    		for (int i = 0; i < n; i++)
    			a[i] = (ll)a[i] * k % p;
    		exp(a, a, n);
    		for (int i = 0; i < n; i++)
    			write(a[i]), putchar(' ');
    		return 0;
    	}
    }
    int main()
    {
    	freopen("5245.in", "r", stdin);
    	return zyt::work();
    }
    

    二、多项式开根

    首先当然可以直接用上面的快速幂,相当于计算 (k)(2) 的逆元时的情况。但有一种常数更小也更好写的方法:

    (B=sqrt A) ,即 (B^2=A) 。和求逆、求指数函数类似,采用分治的思想。假设已经求出 (B_0) 满足 (B_0^2=A mod x^{lceilfrac{n}{2} ceil}) ,求 (B^2=Amod x^n)

    显然有 (B^2=A mod x^{lceilfrac{n}{2} ceil}) ,所以 (B_0-B=0 mod x^{lceilfrac{n}{2} ceil})

    类似求逆,两边同时平方,得到:

    [(B_0-B)^2=0 mod x^n ]

    展开,得到:

    [B_0^2-2B_0B+B^2=0 mod x^n ]

    即:

    [B_0^2-2B_0B+A=0 mod x^n ]

    移项:

    [B=frac{A+B_0^2}{2B_0} mod x^n ]

    多项式求逆即可。

    代码:

    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #include <cctype>
    using namespace std;
    
    namespace zyt
    {
    	template<typename T>
    	inline bool read(T &x)
    	{
    		char c;
    		bool f = false;
    		x = 0;
    		do
    			c = getchar();
    		while (c != EOF && c != '-' && !isdigit(c));
    		if (c == EOF)
    			return false;
    		if (c == '-')
    			f = true, c = getchar();
    		do
    			x = x * 10 + c - '0', c = getchar();
    		while (isdigit(c));
    		if (f)
    			x = -x;
    		return true;
    	}
    	template<typename T>
    	inline void write(T x)
    	{
    		static char buf[20];
    		char *pos = buf;
    		if (x < 0)
    			putchar('-'), x = -x;
    		do
    			*pos++ = x % 10 + '0';
    		while (x /= 10);
    		while (pos > buf)
    			putchar(*--pos);
    	}
    	typedef long long ll;
    	const int N = 1e5 + 10, p = 998244353, g = 3;
    	inline int power(int a, int b)
    	{
    		int ans = 1;
    		while (b)
    		{
    			if (b & 1)
    				ans = (ll)ans * a % p;
    			a = (ll)a * a % p;
    			b >>= 1;
    		}
    		return ans;
    	}
    	inline int get_inv(const int a)
    	{
    		return power(a, p - 2);
    	}
    	namespace Polynomial
    	{
    		const int LEN = N << 2;
    		int rev[LEN], omega[LEN], winv[LEN];
    		void init(const int n, const int lg2)
    		{
    			int w = power(g, (p - 1) / n), wi = get_inv(w);
    			omega[0] = winv[0] = 1;
    			for (int i = 1; i < n; i++)
    			{
    				omega[i] = (ll)omega[i - 1] * w % p;
    				winv[i] = (ll)winv[i - 1] * wi % p;
    			}
    			for (int i = 0; i < n; i++)
    				rev[i] = ((rev[i >> 1] >> 1) | ((i & 1) << (lg2 - 1)));
    		}
    		void ntt(int *a, const int *w, const int n)
    		{
    			for (int i = 0; i < n; i++)
    				if (i < rev[i])
    					swap(a[i], a[rev[i]]);
    			for (int l = 1; l < n; l <<= 1)
    				for (int i = 0; i < n; i += (l << 1))
    					for (int k = 0; k < l; k++)
    					{
    						int x = a[i + k], y = (ll)a[i + l + k] * w[n / (l << 1) * k] % p;
    						a[i + k] = (x + y) % p;
    						a[i + l + k] = (x - y + p) % p;
    					}
    		}
    		void mul(const int *a, const int *b, int *c, const int n)
    		{
    			static int x[LEN], y[LEN];
    			int m = 1, lg2 = 0;
    			while (m < (n << 1) - 1)
    				m <<= 1, ++lg2;
    			memcpy(x, a, sizeof(int[n]));
    			memset(x + n, 0, sizeof(int[m - n]));
    			memcpy(y, b, sizeof(int[n]));
    			memset(y + n, 0, sizeof(int[m - n]));
    			init(m, lg2);
    			ntt(x, omega, m), ntt(y, omega, m);
    			for (int i = 0; i < m; i++)
    				x[i] = (ll)x[i] * y[i] % p;
    			ntt(x, winv, m);
    			int invm = get_inv(m);
    			for (int i = 0; i < n; i++)
    				c[i] = (ll)x[i] * invm % p;
    		}
    		void _inv(const int *a, int *b, const int n)
    		{
    			if (n == 1)
    				b[0] = get_inv(a[0]);
    			else
    			{
    				static int tmp[LEN];
    				int m = 1, lg2 = 0;
    				_inv(a, b, (n + 1) >> 1);
    				while (m < (n << 1) - 1)
    					m <<= 1, ++lg2;
    				init(m, lg2);
    				memcpy(tmp, a, sizeof(int[n]));
    				memset(tmp + n, 0, sizeof(int[m - n]));
    				memset(b + ((n + 1) >> 1), 0, sizeof(int[m - ((n + 1) >> 1)]));
    				ntt(tmp, omega, m), ntt(b, omega, m);
    				for (int i = 0; i < m; i++)
    					b[i] = (b[i] * 2LL % p - (ll)tmp[i] * b[i] % p * b[i] % p + p) % p;
    				ntt(b, winv, m);
    				int invm = get_inv(m);
    				for (int i = 0; i < n; i++)
    					b[i] = (ll)b[i] * invm % p;
    				memset(b + n, 0, sizeof(int[m - n]));
    			}
    		}
    		void inv(const int *a, int *b, const int n)
    		{
    			static int tmp[LEN];
    			memcpy(tmp, a, sizeof(int[n]));
    			_inv(tmp, b, n);
    		}
    		void _sqrt(const int *a, int *b, const int n)
    		{
    			if (n == 1)
    				b[0] = 1;
    			else
    			{
    				static int tmp1[LEN], tmp2[LEN];
    				_sqrt(a, b, (n + 1) >> 1);
    				memset(b + ((n + 1) >> 1), 0, sizeof(int[n - ((n + 1) >> 1)]));
    				mul(b, b, tmp1, n);
    				for (int i = 0; i < n; i++)
    					b[i] = b[i] * 2LL % p;
    				inv(b, tmp2, n);
    				for (int i = 0; i < n; i++)
    					tmp1[i] = (tmp1[i] + a[i]) % p;
    				mul(tmp1, tmp2, b, n);
    			}
    		}
    		void sqrt(const int *a, int *b, const int n)
    		{
    			static int tmp[LEN];
    			memcpy(tmp, a, sizeof(int[n]));
    			_sqrt(tmp, b, n);
    		}
    	}
    	int n, a[N << 2];
    	int work()
    	{
    		read(n);
    		for (int i = 0; i < n; i++)
    			read(a[i]);
    		Polynomial::sqrt(a, a, n);
    		for (int i = 0; i < n; i++)
    			write(a[i]), putchar(' ');
    		return 0;
    	}
    }
    int main()
    {
    	return zyt::work();
    }
    
  • 相关阅读:
    BZOJ 2400: Spoj 839 Optimal Marks (按位最小割)
    bzoj4873: [Shoi2017]寿司餐厅(最大权闭合子图)
    bzoj1497: [NOI2006]最大获利(最大权闭合子图)
    bzoj1607: [Usaco2008 Dec]Patting Heads 轻拍牛头
    bzoj1024: [SCOI2009]生日快乐
    bzoj2761: [JLOI2011]不重复数字
    bzoj1257: [CQOI2007]余数之和sum
    bzoj2456: mode
    bzoj1831: [AHOI2008]逆序对(DP+双精bzoj1786)
    bzoj2431: [HAOI2009]逆序对数列
  • 原文地址:https://www.cnblogs.com/zyt1253679098/p/10657548.html
Copyright © 2020-2023  润新知