bzoj 2734 [HNOI2012]集合选数 构造+状压dp

题面

题目传送门

解法

本题解法十分精妙

构造矩阵,保证每一行是一个公比为3的数列,每一列是一个公比为2的数列

发现这个矩阵最多不超过17行11列

那么我们可以枚举不是2和3公倍数的数放在\((1,1)\)的位置,然后构造出该矩阵

\(f_{i,j}\)表示第\(i\)行选取方案为\(j\)的方案数

转移一下即可

代码

#include <bits/stdc++.h>
#define Mod 1000000001
using namespace std;
template <typename node> void read(node &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;
}
int tx, lim[20], vis[100010], num[20][20], f[20][1 << 12];
int solve(int x) {
	int n = 18, m = 11;
	num[1][1] = x, lim[1] = 0;
	for (int i = 2; i <= n; i++)
		num[i][1] = min(tx + 1, num[i - 1][1] * 2), lim[i] = 0;
	for (int i = 1; i <= n; i++)
		for (int j = 2; j <= m; j++)
			num[i][j] = min(tx + 1, num[i][j - 1] * 3);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			if (num[i][j] <= tx) lim[i] |= 1 << j - 1, vis[num[i][j]] = 1;
	for (int i = 0; i <= n; i++)
		for (int j = 0; j <= lim[i]; j++)
			f[i][j] = 0;
	f[0][0] = 1;
	for (int i = 0; i <= n; i++)
		for (int j = 0; j <= lim[i]; j++) {
			if (!f[i][j]) continue;
			for (int k = 0; k <= lim[i + 1]; k++) {
				if ((j & k) || (k & (k << 1))) continue;
				f[i + 1][k] = (f[i + 1][k] + f[i][j]) % Mod;
			}
		}
	return f[n][0];
}
int main() {
	int ans = 1; read(tx);
	for (int i = 1; i <= tx; i++)
		if (!vis[i]) ans = (1ll * ans * solve(i)) % Mod;
	cout << ans << "\n";
	return 0;
}

posted @ 2018-08-14 23:22  谜のNOIP  阅读(119)  评论(0编辑  收藏  举报