题意:
给你
1
1
1到
n
n
n这
n
n
n个数,问能分成多少种不同的集合,使得每个集合里不存在一个元素
x
x
x和
2
x
2x
2x或者
3
x
3x
3x同时存在。
题解:
这题的核心思想是构造。我们考虑构造一个矩形,左上角的第一个元素是
1
1
1,然后对于每一行,右侧的数是左侧的数的两倍;对于每一列,下方的数是上方的数的三倍。这个矩阵样子如下:
( 1 2 4 8 3 6 12 24 9 18 36 72 27 54 108 216 ) \begin{pmatrix} 1 & 2 &4 & 8\\ 3 & 6 & 12 & 24 \\ 9 & 18 & 36 & 72 \\ 27 & 54 & 108 & 216 \\ \end{pmatrix} ⎝⎜⎜⎛139272618544123610882472216⎠⎟⎟⎞
在这个矩阵中,我们只要保证矩阵中的一个相邻元素不被选到一个集合里就可以了。我们可以用一个类似互不侵犯King的状压dp来计算这个答案,因为这个矩阵的长和宽都是 l o g log log级别的。我们发现这个矩阵并不能包含所有数,于是我们还要找其他没有出现过的数再构造矩阵。每个矩阵相互独立,答案可以根据乘法原理直接相乘。我们其实并不关心矩阵里的数具体是什么,但是我们要被用过的数标记出来,写的时候我们其实只需要知道行数和列数即可。复杂度证法据说很神奇,据 y _ i m m o r t a l y\_immortal y_immortal神仙说严谨证法要积分,不知道是不是真的。
代码:
#include <bits/stdc++.h>
using namespace std;
int n,vis[500010],sta[500010],cnt;
long long ans=1,dp[20][500010],res;
const long long mod=1000000001;
int main()
{
scanf("%d",&n);
for(int i=0;i<=(1<<12)-1;++i)
{
if((i&(i<<1))==0&&(i&(i>>1))==0)
sta[++cnt]=i;
}
int cur=1;
while(cur<=n)
{
if(vis[cur])
{
++cur;
continue;
}
dp[0][1]=1;
res=0;
int i=1,pre=1;
while(1)
{
int x=cur*(1<<(i-1));
if(x>n)
break;
vis[x]=1;
int len=1;
while(x*3<=n)
{
vis[x*3]=1;
++len;
x*=3;
}
int qwq=0;
while((sta[qwq+1]&((1<<len)-1))==sta[qwq+1]&&qwq+1<cnt)
++qwq;
for(int j=1;j<=pre;++j)
{
for(int k=1;k<=qwq;++k)
{
if((sta[j]&sta[k])==0)
dp[i][k]=(dp[i][k]+dp[i-1][j])%mod;
}
}
++i;
pre=qwq;
}
for(int j=1;j<=cnt;++j)
res=(res+dp[i-1][j])%mod;
ans=ans*res%mod;
for(int j=1;j<=i;++j)
{
for(int k=1;k<=cnt;++k)
dp[j][k]=0;
}
++cur;
}
printf("%lld\n",ans);
return 0;
}