LOJ2351「JOI 2018 Final」毒蛇越獄 容斥+子集卷積

題面

題目傳送門

解法

  • 暴力顯然是枚舉??到底填什麼,那麼單次詢問的複雜度爲O(2cnt?)O(2^{cnt_?}),並沒有什麼可以優化的地方。
  • 不妨考慮一個容斥,將??全部看作是11,然後求子集的權值和。但是我們會將某些位置強制爲11卻填成00的數算入答案,那麼我們還要減去這部分的答案。容斥的複雜度爲O(2cnt1)O(2^{cnt_1})
  • 可以發現,因爲nn只到2020,所以min(cnt0,cnt1,cnt?)\min(cnt_0,cnt_1,cnt_?)至多隻有66。那麼我們只要看哪一個最小然後相應地容斥或者暴力枚舉即可。
  • 時間複雜度:O(n×2n+2n3×q)O(n\times2^n+2^{\lfloor\frac{n}{3}\rfloor}\times q)

代碼

#include <bits/stdc++.h>
#define ll long long
using namespace std;
template <typename T> void read(T &x) {
	x = 0; int f = 1; char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
const int N = 1 << 20;
int cnt[N], val[N], s[N][2];
int main() {
	int n, q; read(n), read(q); int m = (1 << n) - 1, t = n / 3;
	for (int i = 1; i <= m; i++) cnt[i] = cnt[i - (i & -i)] + 1;
	for (int i = 0; i <= m; i++) {
		char c = getchar();
		while (!isdigit(c)) c = getchar();
		s[i][0] = s[i][1] = val[i] = c - '0';
	}
	for (int j = 0; j < n; j++)
		for (int i = 0; i <= m; i++)
			if ((i >> j) & 1) s[i][0] += s[i ^ (1 << j)][0], s[i ^ (1 << j)][1] += s[i][1];
	while (q--) {
		int s0 = 0, s1 = 0, s2 = 0, ans = 0;
		char st[30]; scanf(" %s", st);
		for (int i = 0, j = n - 1; i < n; i++, j--)
			if (st[i] == '0') s0 |= (1 << j);
				else if (st[i] == '1') s1 |= (1 << j);
					else s2 |= (1 << j);
		if (cnt[s0] <= t) {
			for (int ts = s0; ; ts = s0 & (ts - 1)) {
				(cnt[ts] & 1) ? ans -= s[s1 | ts][1] : ans += s[s1 | ts][1];
				if (!ts) break;
			}
		} else if (cnt[s1] <= t)
			for (int ts = s1; ; ts = s1 & (ts - 1)) {
				(cnt[ts ^ s1] & 1) ? ans -= s[s2 | ts][0] : ans += s[s2 | ts][0];
				if (!ts) break;
			}
		else
			for (int ts = s2; ; ts = s2 & (ts - 1)) {
				ans += val[s1 | ts];
				if (!ts) break;
			}
		cout << ans << '\n';
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章