problem
题目链接
solution
从最小一个数 xxx 开始,将其 2x,3x2x,3x2x,3x 放入,再将 2(2x),3(2x),2(3x),3(3x)2(2x),3(2x),2(3x),3(3x)2(2x),3(2x),2(3x),3(3x) 放入,以此类推 …\dots… 将其合并为一个集合。重复又找一个最小未进入集合的数 xxx。
显然答案为若干个互不相交,并集为 [1,n][1,n][1,n] 的集合答案的乘积。
一个集合里面的数字是相互制约的,不难发现集合大小不超过 log2n∗log3n\log_2n*\log_3nlog2n∗log3n,大概就是 17×1117\times 1117×11。
考虑怎么计算一个独立集合的可选方案数。
很巧妙的是这个限制只有两个(两倍和三倍),在平面内可以构造出一个矩阵。
矩阵中 (i,j)(i,j)(i,j) 的右边位置放其数值的三倍,下面位置放其数值的两倍。
1 3 9 ...
2 6 18 ...
4 12 36 ...
那么答案就等价为在矩阵中随机选任意个数,要求数两两不相邻。
矩阵行列数都比较小,可以采取状压 dpdpdp 求解。
显然某一行是否合法与矩阵真实长相无关,至于选择的位置有关。
所以提前预处理一行的选择状态,判断是否合法。不合法当且仅当有相邻两列都选了,用 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;
}