这题的做法还挺神的…
题意
- 《集合论与图论》这门课程有一道作业题,要求同学们求出 的所有满足以下条件的子集:若 在该子集中,则 和 不能在该子集中。同学们不喜欢这种具有枚举性 质的题目,于是把它变成了以下问题:对于任意一个正整数 ,如何求出 的满足上述约束条件的子集的个数(只需输出对 取模的结果),现在这个问题就交给你了。
分析
- 我们发现对于某个数 ,如果它选了就有两个数 不能选,于是我们巧妙的构造一个矩阵如下:
- 那么同一个矩阵中相邻的两个数就是不能选的,一行的选择只受上一行的制约。那么我们只要对于所有不是的倍数且不是的倍数的数 都构造一个这样的矩阵,那么不同矩阵里的数一定不重复的且互相不制约,根据乘法原理把每个矩阵的方案数乘起来就是答案了
- 因为要保证数的范围在内,那么矩阵的行数和列数都是级别的,那么求方案就可以用简单的状压DP求了。表示当前到了第行状态为的可行方案数,的每个位置上表示选,表示不选。
则 - 如果状态中选了大于的数或者是状态中同时选了相邻的两个数就不合法。此处 表示这一行与上一行没有同时选中相同的一列。
- 注意一下不要每次都用 清零滚动数组,实测用 慢多了。下面的是用的,上面的使用的清零
- 注意模数
AC代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const int mod = 1e9 + 1;
int N;
int arr[25][15], st[1<<15], tot, f[2][1<<15];
inline int solve(int s) {
int n = 1, m = 1;
memset(arr, -1, sizeof arr);
arr[0][0] = s;
while(arr[0][m-1] * 3 <= N) arr[0][m] = arr[0][m-1] * 3, ++m;
while(arr[n-1][0] * 2 <= N) arr[n][0] = arr[n-1][0] * 2, ++n;
for(int i = 1; i < n; ++i)
for(int j = 1; j < m; ++j)
if(~arr[i-1][j] && arr[i-1][j]*2 <= N) arr[i][j] = arr[i-1][j] * 2;
tot = 0;
for(int state = 0; state < (1<<m); ++state) { //预处理出合法状态
bool flg = 1;
for(int i = 1; i < m && flg; ++i)
if((state>>i)&1 && (state>>(i-1))&1) flg = 0;
if(flg) st[tot++] = state;
}
int now = 0;
for(int j = 0; j < (1<<m); ++j) f[now][j] = 0;
f[now][0] = 1;
for(int i = 0; i < n; ++i) {
now ^= 1;
for(int j = 0; j < (1<<m); ++j) f[now][j] = 0; //for清零
for(int pre = 0, state; pre < tot; ++pre) if(f[now^1][state=st[pre]])
for(int nxt = 0, news; nxt < tot; ++nxt) if(!(state&(news=st[nxt]))) {
bool flg = 1;
for(int j = 0; j < m && flg; ++j)
if(arr[i][j] == -1 && (news>>j)&1) flg = 0; //-1表示这个数大于N
if(flg) f[now][news] = (f[now][news] + f[now^1][state]) % mod;
}
}
int res = 0;
for(int i = 0; i < tot; ++i)
res = (res + f[now][st[i]]) % mod;
return res;
}
int main () {
scanf("%d", &N);
int ans = 1;
for(int i = 1; i <= N; ++i) if((i%2) && (i%3))
ans = 1ll * ans * solve(i) % mod;
printf("%d
", ans);
}