problem
题目链接
solution
从最小一个数 x x x 开始,将其 2 x , 3 x 2x,3x 2x,3x 放入,再将 2 ( 2 x ) , 3 ( 2 x ) , 2 ( 3 x ) , 3 ( 3 x ) 2(2x),3(2x),2(3x),3(3x) 2(2x),3(2x),2(3x),3(3x) 放入,以此类推 … \dots … 将其合并为一个集合。重复又找一个最小未进入集合的数 x x x。
显然答案为若干个互不相交,并集为 [ 1 , n ] [1,n] [1,n] 的集合答案的乘积。
一个集合里面的数字是相互制约的,不难发现集合大小不超过 log 2 n ∗ log 3 n \log_2n*\log_3n log2n∗log3n,大概就是 17 × 11 17\times 11 17×11。
考虑怎么计算一个独立集合的可选方案数。
很巧妙的是这个限制只有两个(两倍和三倍),在平面内可以构造出一个矩阵。
矩阵中 ( i , j ) (i,j) (i,j) 的右边位置放其数值的三倍,下面位置放其数值的两倍。
1 3 9 ...
2 6 18 ...
4 12 36 ...
那么答案就等价为在矩阵中随机选任意个数,要求数两两不相邻。
矩阵行列数都比较小,可以采取状压 d p dp dp 求解。
显然某一行是否合法与矩阵真实长相无关,至于选择的位置有关。
所以提前预处理一行的选择状态,判断是否合法。不合法当且仅当有相邻两列都选了,用 i<<1&i
判断。
之后枚举当前行及操作状态,在合法的基础上,枚举上一行的操作状态,再判断两行之间没有同一列都操作了,用 s&t=0
判断。
能构造矩阵,完全是因为限制一个数的条件只有两个,可以放在二维平面内。这完全就是很投巧的想法。
code
#include <bits/stdc++.h>
using namespace std;
#define mod 1000000001
#define int long long
int N, n, m, ans = 1;
bool vis[100005], g[1 << 20];
int lim[20], a[20][20], f[20][1 << 20];
void init( int x ) {
for( int i = 1;;i ++ ) {
if( i == 1 ) a[i][1] = x;
else a[i][1] = a[i - 1][1] << 1;
if( a[i][1] > N ) break;
else n = i;
vis[a[i][1]] = 1;
lim[i] = 2;
for( int j = 2;;j ++ ) {
a[i][j] = a[i][j - 1] * 3;
if( a[i][j] > N ) break;
else vis[a[i][j]] = 1, lim[i] = 1 << j;
}
}
}
int solve() {
for( int i = 0;i < lim[1];i ++ ) f[1][i] = g[i];
for( int i = 2;i <= n;i ++ )
for( int s = 0;s < lim[i];s ++ ) {
if( ! g[s] ) continue;
f[i][s] = 0;
for( int t = 0;t < lim[i - 1];t ++ )
if( g[t] and ! (s & t) )
f[i][s] = ( f[i][s] + f[i - 1][t] ) % mod;
}
int ret = 0;
for( int i = 0;i < lim[n];i ++ )
ret = ( ret + f[n][i] ) % mod;
return ret;
}
signed main() {
for( int i = 0;i < (1 << 19);i ++ )
g[i] = ! (i << 1 & i);
scanf( "%lld", &N );
for( int i = 1;i <= N;i ++ )
if( ! vis[i] ) init( i ), ans = ans * solve() % mod;
printf( "%lld\n", ans );
return 0;
}