「LOJ6737」指数公式

题目

点这里看题目。


给定一个集合 \(S\subseteq [n]\),其中 \([n]=\{1,2,3,\dots,n-1,n\}\)。设 \(F(x)=\sum_{k\in S}\frac{x^k}{k!}\)

对于 \(1\le k\le n\),计算 \((k![x^k]\exp F(x)) \bmod 2\)

所有数据满足 \(1\le n\le 2\times 10^6\)

分析

这题唯一难点就是如何在 \(\mathbb F_2\) 下算系数。

显然,我们不可能把阶乘逆元这种东西带着计算,所以考虑将一个指数型生成函数表示成 \(\sum_{k\ge 0}f_k\frac{x^k}{k!}\) 的形式。因为最后提取的就是 \(f\),所以这个 \(f\) 可以直接对 \(2\) 取模

在这种意义下,生成函数常见运算的含义是:

  1. \(F(x)+G(x)\) 对应于系数的对应项求和。

  2. \(F(x)G(x)\) 对应于系数的二项卷积

    \[F(x)G(x)=\sum_{n\ge 0}\frac{x^n}{n!}\sum_{k=0}^n\binom{n}{k}f_kg_{n-k} \]

  3. 形式积分和形式微分对应于系数的平移。


有了基础运算后,我们姑且不顾复杂度,直接对于 \(\exp F(x)\) 施加牛顿迭代法。

考察 \(\exp F(x)\) 迭代需要的东西,除了上面的基础运算外还附加一个多项式 \(\ln\)。不过 \(\ln\) 计算时只需要积分、微分和求逆,求逆也可以用牛顿迭代算。

更进一步地,对于形式幂级数 \(F(x)\in \mathbb F_2[[x]]\),如果 \(F(x)\) 本身是可逆的,\(F(x)^2=0\),也即 \(F(x)\) 的逆即为其自身。这一点不难证明。

现在考虑二项卷积如何计算。注意到 \(\dbinom{n}{k}\bmod 2=1\) 当且仅当二进制集合意义下 \(k\subseteq n\),所以形式幂级数的二项卷积与其对应集合幂级数的子集卷积相对应。于是,长度为 \(2^m\) 的两个形式幂级数的二项卷积可以在 \(O(2^mm^2)\) 的时间内计算。

此时,整个的复杂度为 \(O(n\log^2n)\),还不够快。我们注意到子集卷积有一种基于占位多项式的计算方法,此处占位多项式系数为 \(01\),并且度数不高,于是可以想到将系数压成整数,并用其模拟占位多项式的运算,从而做到 \(O(\frac{n\log^2n}{\omega})\)

代码

#include <cstdio>
#include <cstring>

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

const int MAXN = ( 1 << 21 ) + 5;

template<typename _T>
inline void Read( _T &x ) {
	x = 0; char s = getchar(); bool f = false;
	while( ! ( '0' <= s && s <= '9' ) ) { f = s == '-', s = getchar(); }
	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
	if( f ) x = -x;
}

template<typename _T>
inline void Write( _T x ) {
	if( x < 0 ) putchar( '-' ), x = -x;
	if( 9 < x ) Write( x / 10 );
	putchar( x % 10 + '0' );
}

char buf[MAXN];

unsigned F[MAXN];

int N;

inline unsigned Mul( const unsigned &u, const unsigned &v ) {
	if( ! v ) return 0;
	int ret = 0;
	for( unsigned x = v ; x ; x &= x - 1 )
		ret ^= u << __builtin_ctz( x );
	return ret;
}

inline void Transform( unsigned *coe, const int &n ) {
	for( int s = 1 ; s < n ; s <<= 1 )
		for( int i = 0 ; i < n ; i += s << 1 )
			for( int j = 0 ; j < s ; j ++ )
				coe[i | j | s] ^= coe[i | j];
}

namespace PolyInv {
	unsigned P[MAXN], U[MAXN], Q[MAXN], V[MAXN];

	inline void PolyInv( unsigned *ret, const unsigned *A, const int &n ) {
		rep( i, 0, n - 1 ) ret[i] = A[i];
	}
}

namespace PolyLn {
	unsigned P[MAXN], PInv[MAXN];

	inline void PolyLn( unsigned *ret, const unsigned *A, const int &n ) {
		int L = 1;
		for( ; L < n ; L <<= 1 );
		rep( i, 0, L - 1 ) P[i] = PInv[i] = 0;
		rep( i, 1, n - 1 ) P[i - 1] = A[i] << __builtin_popcount( i - 1 );
		PolyInv :: PolyInv( PInv, A, n );
		rep( i, 0, n - 1 ) PInv[i] <<= __builtin_popcount( i );
		Transform( P, L );
		Transform( PInv, L );
		rep( i, 0, L - 1 ) P[i] = Mul( P[i], PInv[i] );
		Transform( P, L );
		rep( i, 0, n - 2 ) ret[i + 1] = P[i] >> __builtin_popcount( i ) & 1;
		ret[0] = 0;
	}
}

namespace PolyExp {
	unsigned P[MAXN], U[MAXN], Q[MAXN], V[MAXN];

	inline void PolyExp( unsigned *ret, const unsigned *A, const int &n ) {
		rep( i, 0, n - 1 ) P[i] = A[i];
		U[0] = 1;
		for( int L = 1 ; L < n ; L <<= 1 ) {
			PolyLn :: PolyLn( Q, U, L << 1 );
			rep( i, 0, L - 1 )
				V[i] = U[i] << __builtin_popcount( i ),
				Q[i] = ( Q[i | L] ^ P[i | L] ) << __builtin_popcount( i );
			Transform( V, L );
			Transform( Q, L );
			rep( i, 0, L - 1 ) V[i] = Mul( V[i], Q[i] );
			Transform( V, L );
			rep( i, 0, L - 1 )
				U[i | L] = V[i] >> __builtin_popcount( i ) & 1;
		}
		rep( i, 0, n - 1 ) ret[i] = U[i];
	}
}

int main() {
	scanf( "%s", buf + 1 );
	N = strlen( buf + 1 );
	int L = 1;
	for( ; L <= N ; L <<= 1 );
	rep( i, 1, N ) F[i] = buf[i] == '1';
	PolyExp :: PolyExp( F, F, L );
	rep( i, 1, N ) putchar( F[i] + '0' );
	puts( "" );
	return 0;
}
posted @ 2023-03-20 16:42  crashed  阅读(75)  评论(0编辑  收藏  举报