【洛谷3226】[HNOI2012] 集合选数(状压DP)
- 给定\(n\),求集合\(\{1,2,...,n\}\)有多少个子集\(S\)满足\(\forall x\in S,2x\notin S\wedge 3x\notin S\)。
- \(n\le10^5\)
构造矩形巧妙求解
说实话我首先想到的是建图,即从每个点\(x\)向\(2x\)和\(3x\)连边,然后就是求图的独立集。
然而这张图虽然边数很少(每个点只会连出两条边),但没有什么有用的特殊性质,不太可做。
但我们发现,每个点有\(\times2\)和\(\times3\)两种转移,而先\(\times2\)再\(\times3\)与先\(\times3\)再\(\times2\)实际上会转移到同一个点。
因此我们不妨构造一个矩形,规定\(\times2\)是向下走,\(\times3\)是向右走。
此时我们仍然是要求独立集,而对于一个\(\log_2n\times \log_3n\)的矩形求独立集,实在再简单不过了。
不过,这里的一个矩形可能并不包含所有元素,实际上要以每个不是\(2,3\)倍数的数为左上角构造矩形,然后将方案数相乘即可。
简单的状压\(DP\)
设\(f_{i,j}\)表示第\(i\)行选择状态为\(j\)的方案数。
转移首先考虑\(j\)是否为一个合法状态,即不存在相邻元素,这只要判断\(j\&(j\texttt{<<}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\n",s),0;//枚举每个不是2,3倍数的数为左上角求解
}
待到再迷茫时回头望,所有脚印会发出光芒