[HNOI2012]集合选数(思维构造 + 状压dp)
problem
題目鏈接
solution
從最小一個數 xxx 開始,將其 2x,3x2x,3x2x,3x 放入,再將 2(2x),3(2x),2(3x),3(3x)2(2x),3(2x),2(3x),3(3x)2(2x),3(2x),2(3x),3(3x) 放入,以此類推 …\dots… 將其合并為一個集合。重復又找一個最小未進入集合的數 xxx。
顯然答案為若干個互不相交,并集為 [1,n][1,n][1,n] 的集合答案的乘積。
一個集合里面的數字是相互制約的,不難發現集合大小不超過 log?2n?log?3n\log_2n*\log_3nlog2?n?log3?n,大概就是 17×1117\times 1117×11。
考慮怎么計算一個獨立集合的可選方案數。
很巧妙的是這個限制只有兩個(兩倍和三倍),在平面內可以構造出一個矩陣。
矩陣中 (i,j)(i,j)(i,j) 的右邊位置放其數值的三倍,下面位置放其數值的兩倍。
1 3 9 ...
2 6 18 ...
4 12 36 ...
那么答案就等價為在矩陣中隨機選任意個數,要求數兩兩不相鄰。
矩陣行列數都比較小,可以采取狀壓 dpdpdp 求解。
顯然某一行是否合法與矩陣真實長相無關,至于選擇的位置有關。
所以提前預處理一行的選擇狀態,判斷是否合法。不合法當且僅當有相鄰兩列都選了,用 i<<1&i 判斷。
之后枚舉當前行及操作狀態,在合法的基礎上,枚舉上一行的操作狀態,再判斷兩行之間沒有同一列都操作了,用 s&t=0 判斷。
能構造矩陣,完全是因為限制一個數的條件只有兩個,可以放在二維平面內。這完全就是很投巧的想法。
code
#include <bits/stdc++.h> using namespace std; #define mod 1000000001 #define int long long int N, n, m, ans = 1; bool vis[100005], g[1 << 20]; int lim[20], a[20][20], f[20][1 << 20];void init( int x ) {for( int i = 1;;i ++ ) {if( i == 1 ) a[i][1] = x;else a[i][1] = a[i - 1][1] << 1;if( a[i][1] > N ) break;else n = i;vis[a[i][1]] = 1;lim[i] = 2;for( int j = 2;;j ++ ) {a[i][j] = a[i][j - 1] * 3;if( a[i][j] > N ) break;else vis[a[i][j]] = 1, lim[i] = 1 << j;}} }int solve() {for( int i = 0;i < lim[1];i ++ ) f[1][i] = g[i];for( int i = 2;i <= n;i ++ )for( int s = 0;s < lim[i];s ++ ) {if( ! g[s] ) continue;f[i][s] = 0;for( int t = 0;t < lim[i - 1];t ++ )if( g[t] and ! (s & t) )f[i][s] = ( f[i][s] + f[i - 1][t] ) % mod;}int ret = 0;for( int i = 0;i < lim[n];i ++ )ret = ( ret + f[n][i] ) % mod;return ret; }signed main() {for( int i = 0;i < (1 << 19);i ++ )g[i] = ! (i << 1 & i);scanf( "%lld", &N );for( int i = 1;i <= N;i ++ )if( ! vis[i] ) init( i ), ans = ans * solve() % mod;printf( "%lld\n", ans );return 0; }總結
以上是生活随笔為你收集整理的[HNOI2012]集合选数(思维构造 + 状压dp)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么在微信写文章(怎么在微信写文章发表)
- 下一篇: [骗分技巧——随机化Ⅰ]CodeChef