【题解】 神仙的游戏 FWT

Legend

...

(校内 OJ 题目不公开)

Link \(\textrm{to HOJ}\)

Editorial

\(n\) 特别小,考虑对每一列状压为 \(n\) 位二进制数。

对于每一行,考虑是否翻转, 则对于每一列可以贪心。但这样做复杂度太高。有没有办法可以一次性求出所有情况的答案?

答案是有,设 \(H(i)\) 为行翻转状态为 \(i\) 的答案,\(F(i)\) 为最初的列为 \(i\) 的列有多少个,\(G(i)\) 表示状态为 \(i\) 的列翻或不翻的最小代价,则:

\[\begin{aligned} H(k)&=\sum_{i=0}^{2^n-1}F(i)G(i\oplus k)\\ &=\sum_{i=0}^{2^n-1}\sum_{j=0}^{2^n-1}[i\oplus k=j]F(i)G(j)\\ &=\sum_{i=0}^{2^n-1}\sum_{j=0}^{2^n-1}[i\oplus j=k]F(i)G(j) \end{aligned} \]

你惊人地发现这就是个异或 FWT!

时间复杂度 \(O(nm+n2^n)\)

Code

#include <bits/stdc++.h>

#define debug(...) ;//fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)\
	freopen(#x".in" ,"r" ,stdin);\
	freopen(#x".out" ,"w" ,stdout)
#define LL long long

const int MX = 1 << 20;
const LL MOD = 1e9 + 7;
const LL inv = (MOD + 1) / 2;

int read(){
	char k = getchar(); int x = 0;
	while(k < '0' || k > '9') k = getchar();
	while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
	return x;
}

void chkmin(int &a ,int b){a = std::min(a ,b);}
int bitcnt(int a){
	int x = 0;
	while(a) a -= a & -a ,++x;
	return x;
}

char str[20][MX];
int n ,m ,cnt[MX];

int G[MX] ,F[MX];
void FWT(int *A ,int limit ,int type){
	for(int mid = 1 ; mid < limit ; mid <<= 1)
		for(int j = 0 ; j < limit ; j += mid << 1)
			for(int k = 0 ; k < mid ; ++k){
				LL x = A[j + k] ,y = A[j + k + mid];
				A[j + k] = (x + y) % MOD;
				A[j + k + mid] = (x - y + MOD) % MOD;
				if(type == -1){
					A[j + k] = A[j + k] * inv % MOD;
					A[j + k + mid] = A[j + k + mid] * inv % MOD;
				}
			}
}

int main(){
	__FILE(game);
	n = read() ,m = read();
	for(int i = 0 ; i < n ; ++i) scanf("%s" ,str[i]);
	for(int j = 0 ; j < m ; ++j){
		int a = 0;
		for(int i = 0 ; i < n ; ++i){
			a |= (str[i][j] == '1') << i;
		}
		++F[a];
	}
	for(int i = 0 ; i < (1 << n) ; ++i){
		int c = bitcnt(i);
		G[i] = std::min(c ,n - c);
	}

	int limit = 1 << n;
	FWT(F ,limit ,1) ,FWT(G ,limit ,1);
	for(int i = 0 ; i < (1 << n) ; ++i) F[i] = 1LL * F[i] * G[i] % MOD;
	FWT(F ,limit ,-1);
	
	int ans = MX;
	for(int i = 0 ; i < (1 << n) ; ++i)
		ans = std::min(ans ,F[i]);
	printf("%d\n" ,ans);	
	return 0;
}
posted @ 2021-03-02 21:10  Imakf  阅读(99)  评论(0编辑  收藏  举报