题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=2734
题意:给出一个由1到n的数字组成的集合。定义合法子集为若x在子集中则2x、3x均不能在子集中。求有多少个合法的子集。
思路:
1 3 9
2 6 18
4 12 36
对于上面的矩阵,我们发现就等价于不选相邻数字的方案数。因此枚举每个还没有用到的数字,建立以该数字为左上角的矩阵。接着就是状态压缩DP。
int a[N][N];
i64 f[2][1<<12];
int n,r,c,h[100005];
void init(int x)
{
r=0,c=0; clr(a,0);
int p1=x,p2,tempC;
while(p1<=n)
{
tempC=0;
for(p2=p1;p2<=n;p2*=3) a[r][tempC++]=p2,h[p2]=1;
upMax(c,tempC);
r++;
p1<<=1;
}
}
int set0(int st,int k)
{
if(st&(1<<k)) return st^(1<<k);
return st;
}
int get(int st,int k)
{
return st&(1<<k);
}
void up(i64 &x,i64 y)
{
x+=y;
if(x>=mod) x-=mod;
}
i64 DP()
{
int pre=0,cur=1,M=1<<c;
int i,j,k,t;
FOR0(i,M) f[pre][i]=0;
f[pre][0]=1;
FOR0(i,r) FOR0(j,c)
{
FOR0(k,M) f[cur][k]=0;
FOR0(t,M) if(f[pre][t])
{
up(f[cur][set0(t,j)],f[pre][t]);
if(!a[i][j]) continue;
if(j==0)
{
if(!(t&1)) up(f[cur][t|1],f[pre][t]);
}
else
{
if(!get(t,j)&&!get(t,j-1)) up(f[cur][t|(1<<j)],f[pre][t]);
}
}
swap(pre,cur);
}
i64 ans=0;
FOR0(i,M) up(ans,f[pre][i]);
return ans;
}
int main()
{
RD(n);
i64 ans=1;
int i;
FOR1(i,n) if(!h[i])
{
init(i);
ans=ans*DP()%mod;
}
PR(ans);
}