「JOI2018」毒蛇越狱

题目

点这里看题目。

分析

感觉已经很久没有正儿八经地写题解了,特意一篇证明我还活着。

这个问题实际上就是要我们求一个子集和,不过数据范围很有诈骗的嫌疑:很容易让人只注意到 \(Q\)\(2^L\),而忽略了 \(L\) 这个参数。

从三个角度来思考这个问题:

  1. 针对 ?,我们可以直接枚举所有的子集并统计答案。这个是最好想的。

  2. 针对 1,我们可以把所有 ? 全部看成 1,预处理高维前缀和之后进行容斥。这个也不难想。

  3. 根据对称性,针对 0 则可以把 ? 全部看成 0,预处理高位后缀和进行容斥。这个并不难想,但是很容易忘记,应当细心一点。

困难的地方不在于想到上面三个算法之一,也不在于想到上面的三个算法的全部,而是想到可以针对数据分治。注意到,三个算法任意一个单独拿出来都可以直接解决问题,并且复杂度都形如 \(O(2^{cnt})\),因此直接将它们拼在一起就可以得到 \(O(2^{\lfloor\frac{L}{3}\rfloor})\) 的算法。


还有一个伪了的想法,记在这里。

将询问的字符串描述成 \((y,z)\),其中 \(z\) 描述了可变动的子集,而 \(y\) 描述了必须匹配上的部分。则询问的答案就是:

\[\sum_{(x\oplus y)\& z=(x\oplus y)}A_x \]

修改成卷积的形式:

\[\sum_{m\oplus x=y}A_x[m\& z = z] \]

这样一来,我们的询问相当于给定两个参数并且算一个异或卷积的单点值。尝试手算 FWT。则:

\[\operatorname{FWT}(\{[m\& z = z]\})[k]=[k\& z = 0]2^{\operatorname{popcount}(z)} \]

另一边,\(A\) 的 FWT 结果可以预处理。最终就是要算:

\[2^{\operatorname{popcount}(z)-L}\sum_{x\& z = 0}(-1)^{\operatorname{popcount}(x\& y)}\operatorname{FWT}(A)[x] \]

然后就已经没戏了。考虑到转化之后求的是二元函数的单点值,并且我们至今还没有成功地消除某个变量的影响,也不太可能枚举某个变量并预处理,这个算法基本上是没有前途的。😢

小结:

  1. 保留对问题的全局观念。尤其是复杂度平衡的题目,能够想到各个算法不难,但是能不能想到把多个独立的算法拼起来又是另一会事了。

  2. 眼光放宽一点,格局再打开一点。不要拘泥于 \(2^L\)\(Q\) 这种看起来比较“实质”的数据范围,不然就被诈骗了。

代码

#include <cstdio>
#include <iostream>
#include <algorithm>

#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 << 20, MAXL = 25;

template<typename _T>
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>
void write( _T x ) {
	if( x < 0 ) putchar( '-' ), x = -x;
	if( 9 < x ) write( x / 10 );
	putchar( x % 10 + '0' );
}

int A[MAXN], su[MAXN], suRev[MAXN];
char buf[MAXN];

int N, L, Q;

int main() {
	read( L ), read( Q );
	scanf( "%s", buf ), N = 1 << L;
	rep( i, 0, N - 1 ) suRev[i] = su[i] = A[i] = buf[i] ^ '0';
	for( int s = 1 ; s < N ; s <<= 1 )
		for( int i = 0 ; i < N ; i += s << 1 )
			for( int j = 0 ; j < s ; j ++ )
				su[i | j | s] += su[i | j],
				suRev[i | j] += suRev[i | j | s];
	while( Q -- ) {
		scanf( "%s", buf );
		std :: reverse( buf, buf + L );	
		int val0 = 0, val1 = 0, val_ = 0;
		rep( i, 0, L - 1 ) {
			if( buf[i] == '0' ) val0 |= 1 << i;
			if( buf[i] == '1' ) val1 |= 1 << i;
			if( buf[i] == '?' ) val_ |= 1 << i;
		}
		int ans = 0;
		int cnt0 = __builtin_popcount( val0 );
		int cnt1 = __builtin_popcount( val1 );
		int cnt_ = __builtin_popcount( val_ );
		if( cnt_ == std :: min( std :: min( cnt0, cnt1 ), cnt_ ) ) {
			ans = A[val1];
			for( int T = val_ ; T ; T = ( T - 1 ) & val_ )
				ans += A[val1 | T];
		} else if( cnt1 == std :: min( std :: min( cnt0, cnt1 ), cnt_ ) ) {
			ans = su[val1 | val_];
			for( int T = val1 ; T ; T = ( T - 1 ) & val1 ) {
				int res = su[( val1 ^ T ) | val_];
				__builtin_parity( T ) ? ans -= res : ans += res;
			}
		} else {
			ans = suRev[val1];
			for( int T = val0 ; T ; T = ( T - 1 ) & val0 ) {
				int res = suRev[T | val1];
				__builtin_parity( T ) ? ans -= res : ans += res;
			}
		}
		write( ans ), putchar( '\n' );
	}
	return 0;
}


posted @ 2022-05-04 15:08  crashed  阅读(44)  评论(0编辑  收藏  举报