「JOI2018」毒蛇越狱
题目
点这里看题目。
分析
感觉已经很久没有正儿八经地写题解了,特意水一篇证明我还活着。
这个问题实际上就是要我们求一个子集和,不过数据范围很有诈骗的嫌疑:很容易让人只注意到 \(Q\) 和 \(2^L\),而忽略了 \(L\) 这个参数。
从三个角度来思考这个问题:
-
针对
?
,我们可以直接枚举所有的子集并统计答案。这个是最好想的。 -
针对
1
,我们可以把所有?
全部看成1
,预处理高维前缀和之后进行容斥。这个也不难想。 -
根据对称性,针对
0
则可以把?
全部看成0
,预处理高位后缀和进行容斥。这个并不难想,但是很容易忘记,应当细心一点。
困难的地方不在于想到上面三个算法之一,也不在于想到上面的三个算法的全部,而是想到可以针对数据分治。注意到,三个算法任意一个单独拿出来都可以直接解决问题,并且复杂度都形如 \(O(2^{cnt})\),因此直接将它们拼在一起就可以得到 \(O(2^{\lfloor\frac{L}{3}\rfloor})\) 的算法。
还有一个伪了的想法,记在这里。
将询问的字符串描述成 \((y,z)\),其中 \(z\) 描述了可变动的子集,而 \(y\) 描述了必须匹配上的部分。则询问的答案就是:
修改成卷积的形式:
这样一来,我们的询问相当于给定两个参数并且算一个异或卷积的单点值。尝试手算 FWT。则:
另一边,\(A\) 的 FWT 结果可以预处理。最终就是要算:
然后就已经没戏了。考虑到转化之后求的是二元函数的单点值,并且我们至今还没有成功地消除某个变量的影响,也不太可能枚举某个变量并预处理,这个算法基本上是没有前途的。😢
小结:
-
保留对问题的全局观念。尤其是复杂度平衡的题目,能够想到各个算法不难,但是能不能想到把多个独立的算法拼起来又是另一会事了。
-
眼光放宽一点,格局再打开一点。不要拘泥于 \(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;
}