- 给定(n),求集合({1,2,...,n})有多少个子集(S)满足(forall xin S,2x otin Swedge 3x otin S)。
- (nle10^5)
构造矩形巧妙求解
说实话我首先想到的是建图,即从每个点(x)向(2x)和(3x)连边,然后就是求图的独立集。
然而这张图虽然边数很少(每个点只会连出两条边),但没有什么有用的特殊性质,不太可做。
但我们发现,每个点有( imes2)和( imes3)两种转移,而先( imes2)再( imes3)与先( imes3)再( imes2)实际上会转移到同一个点。
因此我们不妨构造一个矩形,规定( imes2)是向下走,( imes3)是向右走。
此时我们仍然是要求独立集,而对于一个(log_2n imes log_3n)的矩形求独立集,实在再简单不过了。
不过,这里的一个矩形可能并不包含所有元素,实际上要以每个不是(2,3)倍数的数为左上角构造矩形,然后将方案数相乘即可。
简单的状压(DP)
设(f_{i,j})表示第(i)行选择状态为(j)的方案数。
转移首先考虑(j)是否为一个合法状态,即不存在相邻元素,这只要判断(j&(j exttt{<<}1))是否为(0)即可。
然后在(j)的补集中枚举子集,表示上一行状态(k),给(f_{i,j})加上(f_{i-1,k}),就完成了转移。
代码:(O(nlogn))
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define L2 17
#define L3 11
#define X 1000000001
using namespace std;
int n;I int Calc(RI x) {RI t=0;W(x) x/=3,++t;return t;}
int a[L2+5],f[L2+1][1<<L3];I int Solve(CI x)
{
RI i,j,k,l,t=0;for(i=1;i<=x;i<<=1) a[++t]=Calc(x/i);//求出每行的项数
for(f[0][0]=i=1;i<=t;++i) for(j=0;j^(1<<a[i]);++j) if(!(j&(j<<1)))//枚举每一行的每个合法状态
for(f[i][j]=f[i-1][0],k=l=((1<<a[i-1])-1)^j;k;k=(k-1)&l) f[i][j]=(f[i][j]+f[i-1][k])%X;//在补集中枚举子集
RI s=0;for(i=0;i^(1<<a[t]);++i) s=(s+f[t][i])%X;return s;//统计答案
}
int main()
{
RI i,s=1;for(scanf("%d",&n),i=1;i<=n;++i) i%2&&i%3&&(s=1LL*s*Solve(n/i)%X);return printf("%d
",s),0;//枚举每个不是2,3倍数的数为左上角求解
}