【[HNOI2012]集合选数】数学构造+状态压缩
在和xiaoaoao交流之下其实很早就想到了正解,,然而在一些地方纠缠不清加有点怂一直没敢写出来,为此又颓废了一天。orz
BZOJ2734非权限题 Luogu3226
2734: [HNOI2012]集合选数
Time Limit: 10 Sec Memory Limit: 128 MB Submit: 1624 Solved: 964 [Submit][Status][Discuss]Description
《集合论与图论》这门课程有一道作业题,要求同学们求出{1, 2, 3, 4, 5}的所有满足以 下条件的子集:若 x 在该子集中,则 2x 和 3x 不能在该子集中。同学们不喜欢这种具有枚举性 质的题目,于是把它变成了以下问题:对于任意一个正整数 n≤100000,如何求出{1, 2,..., n} 的满足上述约束条件的子集的个数(只需输出对 1,000,000,001 取模的结果),现在这个问题就 交给你了。
Input
只有一行,其中有一个正整数 n,30%的数据满足 n≤20。
Output
仅包含一个正整数,表示{1, 2,..., n}有多少个满足上述约束条件 的子集。
Sample Input
4
Sample Output
8
【样例解释】
有8 个集合满足要求,分别是空集,{1},{1,4},{2},{2,3},{3},{3,4},{4}。
我们考虑将这些数分化,由于每个数x是否可选只与 (x/2) (x/3) (x*2) (x*3) 有关,于是我们考虑到这个可以构造出一个矩阵
【1*x】【3*x】【9*x】 【27*x】 。。。。。
【2*x】【6*x】 【18*x】 .......
【4*x】【12*x】 【36*x】.....
【8*x】......
.....
也就是矩阵的四周分别是我们提到的那些有关数,这样问题就转化为了在矩阵(事实上由于有n的限制其应该为一个锯齿状)里面选取不相邻元素的方案数。
我们可以显然可见,一直乘3,长度不会超过11,宽度也不会太多。我们具体解决的时候运用类似炮兵阵地Luogu炮兵阵地的思路搞一个DP或者记忆化搜索(事实上代码能力很弱的我只写得出记忆化)就可以了。由于对于一个矩阵其会有许多数没有包含进去。而对于这些没有包含的,我们对其一个一个建立矩阵然后DP。
由于这些彼此矩阵之间的数不会重复,也即恰好n个数建立完所有矩阵。而彼此矩阵之间不会有联系(我这边选了方案对方矩阵的方案数不受影响)我们直接乘法原理将所有矩阵方案数相乘即可。
稍微卡卡常也就过去了(当这一排的某个数超过n,这一排接下来的数就不需要构造了)
code:
//%%% #include<bits/stdc++.h> typedef long long ll; using namespace std; const ll mod = 1000000001LL; ll n,S; ll dp[20][1<<12]; ll jz[20][20]; bool mark[20][1<<12]; ll pos[20]; ll ans=1; bool bb[100005]; bool check(ll now,ll last,ll pai) { if( ( !(now&last) ) && ( (pos[pai]|now) == pos[pai] ) && ( !(now&(now>>1)) ) ) return true; else return false; } ll dfs(ll &limit,ll pai,ll now) { if(mark[pai][now]) return dp[pai][now]%mod; mark[pai][now]=1; if(pai==limit) return dp[pai][now]=1; ll sum=0; for(ll i=S;i>=0;i--) { if(check(i,now,pai+1)) { sum = (sum + dfs(limit,pai+1,i))%mod; } } return dp[pai][now] = sum; } void gzdp(ll x) { memset(pos,0,sizeof pos); ll chang=x,cc=1,kuan=x,kk=1; while(chang*3<=n) ++cc,chang*=3; while(kuan*2<=n) ++kk,kuan*=2; jz[1][1]=x; bb[x]=1; for(int i=2;i<=cc;i++) jz[1][i]=jz[1][i-1]*3,bb[jz[1][i]]=1; for(int i=2;i<=kk;i++) { jz[i][1]=jz[i-1][1]*2; bb[jz[i][1]]=1; pos[i]|=1; for(int j=2;j<=cc;j++) { jz[i][j]=jz[i][j-1]*3; if(jz[i][j]>n) break; else { bb[jz[i][j]]=1; pos[i] |= (1<<(j-1)); } } } S=(1<<cc)-1; pos[1]=S; ll sum = 0; for(int s=0;s<=S;s++) { if(check(s,0,1)) sum = ( sum + dfs( kk , 1,s ) )%mod; } ans = ans * sum%mod; for(ll i=1;i<=kk;i++) { for(ll j=0;j<=S;j++) { dp[i][j]=0; mark[i][j]=0; } } } int main() { scanf("%lld",&n); for(ll i=1;i<=n;i++) { if(!bb[i]) gzdp(i); } printf("%lld",ans); }