[BZOJ 2734] 集合选数
Link:
Solution:
真是奥妙重重的建模啊.....
我们发现$x,2*x,3*x$这些数太分散了,难以处理
于是我们构建这样的表格:
x 3x 9x 27x....
2x 6x 18x 54x...
4x 12x 36x 108x...
... ..... ..... ......
将问题转化为求表格上任意两点不相邻的点集数。
由于$2^{17}>100000$,因此直接对每一行的信息状压就行了
设$dp[i][j]$为到第$i$行且其状态为$j$时的方案总数,上一层状态为$k$,判断!(k&j) && !(j&(j>>1))即可
Note:由于一个表格不能包含所有数,因此要建多个表格,结果相乘
Code:
//by NewErA #include <bits/stdc++.h> using namespace std; typedef long long ll; const int MOD=1e9+1; const int MAXN=100005; ll n,dp[50][MAXN],dat[50][50],len[50],last; bool vis[MAXN]; ll cal(int x) { dat[1][1]=x; for(int i=1;;i++) { if(i>1) dat[i][1]=dat[i-1][1]*2; if(dat[i][1]>n){last=i-1;break;} vis[dat[i][1]]=true; for(int j=2;;j++) { dat[i][j]=dat[i][j-1]*3; if(dat[i][j]>n){len[i]=j-1;break;} vis[dat[i][j]]=true; } } for(int i=0;i<=last+1;i++) for(int j=0;j<=(1<<len[i]);j++) dp[i][j]=0; dp[0][0]=1;len[0]=1; //状压DP for(int i=0;i<=last;i++) for(int j=0;j<(1<<len[i]);j++) if(dp[i][j]) for(int k=0;k<(1<<len[i+1]);k++) if(!(j&k) && !(k&(k>>1))) dp[i+1][k]=(dp[i+1][k]+dp[i][j])%MOD; return dp[last+1][0]; } int main() { cin >> n; ll res=1; for(int i=1;i<=n;i++) if(!vis[i]) res=res*cal(i)%MOD; cout << res; return 0; }
Review:
1、当信息较为分散时,通过构建矩阵等各类方式将相关条件集中处理
2、求矩阵中任意两点不相邻的点集数(经典问题)
状压DP的应用,整体$!(j&k)$和$!k&(k>>1)$判断的方式还行