题意:洛谷P3226
分析:
很巧妙的构造题!
构造思路:构造一个矩阵,矩阵的每行的元素都是前一行元素的
2
2
2倍,每列的元素都是前一列的
3
3
3倍。 例如:
1
,
3
,
9
,
27
1,3,9,27
1,3,9,27
2
,
6
,
18
,
54
2,6,18,54
2,6,18,54
4
,
12
,
36
,
108
4,12,36,108
4,12,36,108
观察这个矩阵可以发现,如果选取了一个数,那么这个数的上下左右就不能取了,那么这不就是个经典状压
d
p
dp
dp吗?但有一个问题是,构造的矩阵不一定包含所有的数,所以我们要对全部未构成矩阵的数构造矩阵。最后我们构造的很多个矩阵一定是没有相同元素的,所以总的方案数就是每个构造的矩阵的方案数的乘积。
(构造的时候令每列是前一列的
3
3
3倍,状态数会少一点,跑得更快。)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 1000 + 5;
const int inf = 0x3f3f3f3f;
const LL mod = 1000000001;
const LL INF = 0x3f3f3f3f3f3f3f3f;
int n, r, len, c[20];
LL ans, mat[20][20], dp[20][(1 << 17) + 5];
bool vis[(1 << 17) + 5], can[(1 << 17) + 5];
void getMat(LL x){//用r表示最大行数,c[i]表示每行的列数
memset(c, 0, sizeof c), r = 0;
for(int i = 1; i <= 20; i++){
if(i == 1) mat[i][1] = x;
else mat[i][1] = mat[i - 1][1] * 2;
if(mat[i][1] > n) break;
r++, c[i] = 1, vis[mat[i][1]] = true;
for(int j = 2; j <= 20; j++){
mat[i][j] = mat[i][j - 1] * 3;
if(mat[i][j] > n) break;
c[i] = j, vis[mat[i][j]] = true;
}
}
}
LL solve(int r){
for(int i = 0; i < (1 << c[1]); i++) if(can[i]) dp[1][i] = 1;
for(int i = 2; i <= r; i++)
for(int j = 0; j < (1 << c[i]); j++) if(can[j]){
dp[i][j] = 0;
for(int k = 0; k < (1 << c[i - 1]); k++) if(can[k] && !(k & j))
dp[i][j] += dp[i - 1][k] % mod;
}
LL res = 0;
for(int i = 0; i < (1 << c[r]); i++) res += dp[r][i] % mod;
return res;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
len = log2(n);
for(int i = 0; i < (1 << len); i++) if(!(i & (i << 1))) can[i] = true;//预处理可行状态
ans = 1;
for(int i = 1; i <= n; i++) if(!vis[i]){//对未构成矩阵的数 构造矩阵
getMat(i);
ans = ans * solve(r) % mod;
}
cout << ans << '\n';
return 0;
}