[HDU6129]Just do it

题目

传送门 to HDU

思路壹

一开始有这样一个思路,考虑每个数字最终会给哪些位置提供异或值。用 f ( i , m ) f(i,m) 表示下标之差为 i i ,经过 m m 次操作后,是否提供贡献。显然可以模仿快速幂,有

f ( i , m ) = j = 0 i f ( j , m 0 ) f ( i j , m m 0 ) f(i,m)=\sum_{j=0}^{i}f(j,m_0)f(i-j,m-m_0)

最后取模 2 2 ,因为异或只在乎奇偶性。这是卷积的式子,可以做到 O ( n log n ) \mathcal O(n\log n) ,所以计算 f ( i , m ) f(i,m) O ( n log n log m ) \mathcal O(n\log n\log m) 的。

有了这个,我们再模仿这个思路,想办法让原数组变成 0 / 1 0/1 ,就可以卷积了。拆位即可。复杂度 O ( n log n log a ) \mathcal O(n\log n\log a)

思路贰

经过别人的提醒,意识到 f ( i , m ) f(i,m) 是一个组合数。为何?上面那个式子像不像是经典组合恒等式

( n + m r ) = i = 0 + ( n i ) ( m r i ) {n+m\choose r}=\sum_{i=0}^{+\infty}{n\choose i}{m\choose r-i}

我可以直接告诉你结论, f ( i , m ) = ( i + m 1 m 1 ) f(i,m)={i+m-1\choose m-1}

自己推式子怎么办?可以考虑递推式在 m 0 = 1 m_0=1 时的样子,即

f ( i , m ) = j = 0 i f ( j , m 1 ) f(i,m)=\sum_{j=0}^{i}f(j,m-1)

再处理一下,应当为

f ( i , m ) = f ( i , m 1 ) + j = 0 i 1 f ( j , m 1 ) = f ( i , m 1 ) + f ( i 1 , m 1 ) \begin{aligned} f(i,m)&=f(i,m-1)+\sum_{j=0}^{i-1}f(j,m-1)\\ &=f(i,m-1)+f(i-1,m-1) \end{aligned}

这不就是一个杨辉三角吗?

我们只需要算组合数对 2 2 取模的结果就行了。根据卢卡斯定理, ( n m ) m o d 2 = 1 {n\choose m}\bmod 2=1 当且仅当二进制表示下 m m n n 的“子集”。

复杂度变成了 O ( n log n log a ) \mathcal O(n\log n\log a)

然后就 T L E \tt TLE 了。

代码

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int x){
	if(x > 9) writeint(x/10);
	putchar((x%10)^48);
}
inline int qkpow(int_ b,int q,int m){
	int ans = 1;
	for(; q; q>>=1,b=b*b%m)
		if(q&1) ans = ans*b%m;
	return ans;
}

const int Mod = 998244353;
int omg[50]; // omg[x] = 3^{(Mod-1)/(2^x)}
int inv[50]; // inv[x]*omg[x] = 1
int pow2[50]; // pow2[x]*(2^x) = 1
void prepare_NTT(){
	int g = Mod-1, cnt = 0;
	while(!(g&1)) g >>= 1, ++ cnt;
	omg[cnt] = qkpow(3,g,Mod);
	inv[cnt] = qkpow(3,Mod-1-g,Mod);
	for(int i=cnt; i>=1; --i){
		omg[i-1] = 1ll*omg[i]*omg[i]%Mod;
		inv[i-1] = 1ll*inv[i]*inv[i]%Mod;
	}
	pow2[0] = 1; // 2^0 = 1
	g = pow2[1] = (Mod+1)>>1;
	for(int i=2; i<=cnt; ++i)
		pow2[i] = 1ll*pow2[i-1]*g%Mod;
}
const int MaxN = 400005;
int turn[MaxN];
void NTT(int a[],int n,int opt){
	for(int i=1; i<(1<<n); ++i){
		turn[i] = turn[i>>1]>>1;
		if(i&1) turn[i] ^= 1<<n>>1;
		if(i < turn[i])
			swap(a[i],a[turn[i]]);
	}
	int logN = n; // 暂存
	n = 1<<n; // 真正的长度
	int *zxy = opt == 1 ? omg : inv;
	for(int i=1; (1<<i)<=n; ++i){
		int l = 1<<i>>1, w;
		for(int *p=a; p!=a+n; p+=l*2){
			w = 1; // 不能在外面赋初值!
			for(int j=0; j<l; ++j){
				int t = 1ll*w*p[j+l]%Mod;
				p[j+l] = (Mod-t+p[j])%Mod;
				p[j] = (p[j]+t)%Mod;
				w = 1ll*w*zxy[i]%Mod;
			}
		}
	}
	if(opt == -1)
	for(int i=0; i<n; ++i)
		a[i] = 1ll*a[i]*pow2[logN]%Mod;
}

int f[MaxN], g[MaxN];
int a[MaxN], res[MaxN];
int main(){
	// C(i+m-1,m-1)
	prepare_NTT();
	for(int T=readint(); T; --T){
		int n = readint(), m = readint();
		for(int i=0; i<n; ++i)
			a[i] = readint();
		int N = 0; // 实际上是存的 log
		while((1<<N) < n) ++ N;
		for(int i=0; i<(1<<N); ++i)
			f[i] = ((i+m-1)&(m-1)) == m-1;
		NTT(f,N,1);
// printf("f_ =");
// for(int i=0; i<(1<<N); ++i)
// 	printf(" %d",f[i]);
// putchar('\n');
		for(int i=0; i<30; ++i){
			for(int j=n; j<(1<<N); ++j)
				g[j] = 0; // clear
			for(int j=0; j<n; ++j)
				g[j] = a[j]>>i&1;
			NTT(g,N,1);
// printf("g_ =");
// for(int j=0; j<(1<<N); ++j)
// 	printf(" %d",g[j]);
// putchar('\n');
			for(int j=0; j<(1<<N); ++j)
				g[j] = 1ll*f[j]*g[j]%Mod;
			NTT(g,N,-1);
			for(int j=0; j<n; ++j){
				res[j] -= res[j]&(1<<i);
				if(g[j]&1)
					res[j] ^= 1<<i;
			}
		}
		for(int i=0; i<n; ++i){
			writeint(res[i]);
			putchar(' ');
		}
		putchar('\n');
	}
	return 0;
}

思路叁

看了这篇题解,心有所悟。

要注意到这个规律: f ( i , 2 k ) = [ 2 k i ] f(i,2^k)=[2^k|i] 。也就是说,操作 2 k 2^k 次时,只有相差为 2 k 2^k 的倍数才会提供贡献。这相当于按照模 2 k 2^k 的余数分类,然后每一个做一次前缀异或和。

那么我们把 m m 拆成二进制位。结束。复杂度 O ( n log m ) \mathcal O(n\log m) 。(实际上是 n log n n\log n 才对。)

猜你喜欢

转载自blog.csdn.net/qq_42101694/article/details/108445508
今日推荐