BZOJ 2734[HNOI2012]集合选数
题意: 给一个n,求{1~n}的所有满足以下条件的子集:若 x 在该子集中,则 2x 和 3x 不能在该子集中。
很神奇的一道题,刚开始以为是数学,还想了一下筛法什么的。。。但是只要稍加思索就会发现,对于一个元素,
有取与不取两个状态,如果用01状态表示取与不取,就是一个状压dp了,再看一看复杂度,靠谱。
但是怎样满足题设条件呢???
表示不会搜了题解,然后发现这题思想神的一逼:
对于一个数x,构造一个矩阵
x,3x,9x......
2x,6x,18x......
4x,12x,36x......
于是就会发现这道题神奇的转化成了经典问题:互不侵犯king,上下左右相邻数不可同时取到,用状压搞之,过了。
notice:数组计算好,是18*11的,有个同学,开数组开大了,空间上可以过,但是用memset清零超时了,一定不要多开!!!!!
#include<iostream> #include<cstdio> #include<cstring> using namespace std; typedef long long ll; const int MOD=1000000001; bool pd[100001]; int map[20][20],n,s[20],base[20]; ll dp[20][1<<11],ans=1ll; inline void make(int x) { memset(map,0,sizeof(map)); for(int i=1,h=x;h<=n;i++,h=h*2) { map[i][1]=h; pd[h]=1; for(int j=2,z=h*3;z<=n;j++,z=z*3) { map[i][j]=z; pd[z]=1; } } memset(s,0,sizeof(s)); for(int i=1;i<=18;i++) for(int j=1;j<=11;j++) if(map[i][j]!=0) { pd[map[i][j]]=1; s[i]+=base[j-1]; } } inline int DP(int x) { memset(dp,0,sizeof(dp)); dp[0][0]=1; for(int i=0;i<18;i++) for(int j=0;j<=s[i];j++) if(dp[i][j]) for(int k=0;k<=s[i+1];k++) if((!(j&k))&&(!(k&(k>>1)))) dp[i+1][k]=(dp[i][j]+dp[i+1][k])%MOD; return dp[18][0]; } inline void solve() { for(int i=1;i<=n;i++) if(!pd[i]) { make(i); ans=ans*DP(i)%MOD; } printf("%lld",ans%MOD); } int main() { base[0]=1; for(int i=1;i<20;i++)base[i]=(base[i-1]<<1); scanf("%d",&n); solve(); }