1. 程式人生 > >題解 P3226 【[HNOI2012]集合選數】

題解 P3226 【[HNOI2012]集合選數】

構造 需要 amp std += 內部 lld using cpp

一道非常妙的構造題 \(QwQ\)

\(2x\)\(3x\) 都不在集合內,但直接處理貌似非常不好,所以我們需要構造出一個與原命題等價的命題。

貌似是這個,構造一個矩形,第一行第一列的元素為\(1\),第一行的後面所有數均為前面的數的兩倍。

接下來每列的數都是它上面的數的三倍。

大概構造出來長這樣:

1  2  4  8   16  32  ...
3  6  12 24  48  96  ...
9  18 36 72  ...
27 ...

那麽對於這個矩形內,我們要做的就是求出這個矩形內,選出一些數,相鄰的數不能選的方案數。

因為每個數都是前面那個數的 \(2\) 倍,所以這個矩形最後長為 \(log_2n\)

,寬為\(log_3n\)大概是 \(17,12\) 不到的樣子。

可以用狀壓\(dp\)解決

但是這個矩形並沒有涵蓋所有的數,所以我們需要對每個即不是 \(2\) 的倍數又不是 \(3\) 的倍數的數都類似的構造矩形,可以發現矩形內部元素不重復,然後根據乘法原理,將答案相乘即可。

#include<bits/stdc++.h>
using namespace std;
int read() {
    char cc = getchar(); int cn = 0, flus = 1;
    while(cc < '0' || cc > '9') {  if( cc == '-' ) flus = -flus;  cc = getchar();  }
    while(cc >= '0' && cc <= '9')  cn = cn * 10 + cc - '0', cc = getchar();
    return cn * flus;
}
#define int long long
#define rep( i, s, t ) for( register int i = s; i <= t; ++ i ) 
const int mod = 1000000001;
const int maxn = ( 1 << 18 ) - 1;
const int N = 100000 + 5;
const int M = 20 ;
int n, book[N], Ans, line[M], g[maxn], a[M][M], end, dp[M][maxn], num, lim[M];
void init( int x ) {
    rep( i, 1, 11 ) {
        if( i == 1 ) a[i][1] = x;
        else a[i][1] = a[i - 1][1] * 3;
        //初始化矩形 
        if( a[i][1] > n ) break ;
        
        end = i, line[i] = 1, book[a[i][1]] = 1;
        //line表示第i行有多少列 
        rep( j, 2, 18 ) {
            a[i][j] = a[i][j - 1] * 2;
            if( a[i][j] > n ) break;
            line[i] = j, book[a[i][j]] = 1; // 用book標記這個元素被選過 
        }
        lim[i] = ( 1 << line[i] ) - 1; // lim表示第i行的數有多少個,起限制作用 
    }
}
void solve(int x) {
    num = 0 ;
    rep( i, 0, lim[1] ) dp[1][i] = g[i];
    rep( i, 2, end ) rep( j, 0, lim[i] ) {
        if( !g[j] ) continue ; //如果狀態j不合法,就跳過 
        dp[i][j] = 0;
        rep( k, 0, lim[i - 1] )
            if( g[k] && ( (k & j) == 0 ) ) //如果狀態k合法,且k與j沒有位置相同 
            dp[i][j] += dp[i - 1][k], dp[i][j] %= mod;
    }
    rep( i, 0, lim[end] ) num += dp[end][i], num %= mod ;
}
signed main()
{
    n = read() ; Ans = 1;
    rep( i, 0, maxn ) g[i] = ( (i << 1) & (i) ) ? 0 : 1; //初始化哪些狀態合法。 
    
    rep( i, 1, n ) if( !book[i] )  //如果這個數沒有被選過。 
        init(i), solve(i), Ans = Ans * num % mod; //先構造矩形,然後狀壓dp 
    
    printf("%lld\n", Ans );
    return 0;
}

題解 P3226 【[HNOI2012]集合選數】