【BZOJ 2734】集合取數

1.題目鏈接。這個題其實還是一個不錯的題目,思路比較新穎。首先構造一個矩陣:

                                

這個矩陣的構造規則是這樣的:從左上角開始,a[1][1]=st,a[i][j+1]=3*a[i][j],a[i+1][j]=2*a[i][j].也就是右邊的數都是它的三倍,做別的數都是他的兩倍,然後得到了這樣的一個矩陣。可以發現,這個矩陣裏面出現了很多的1-n中的的數。首先考慮第一個矩陣,就是用1爲左上角頂點構造的。那麼可以發現,我們選取的方案就是:不選取相鄰的數即可。可以發現,這個矩陣其實不會太大,20*20都不會超過。所以,可以把每一行的狀壓一下,然後考慮dp[i[j]表示:前i行,第i行的狀態是j的方案,這個其實就很好轉移了:

                                                     dp[i][j]=\sum dp[i-1][k]

但是這個轉移顯然是需要條件的,很明顯第一個條件就是j必須是合法的狀態,也就是j不能是選取兩個相鄰的數的轉態,這個把j>>1&j 就可以判斷,這是行上的約束,接下來就是列上的約束,這個也很好判斷,遍歷所有的i-1的狀態,k和j不能有同一行一樣,也就是j&k需要==0,這樣只是完成了第一步,因爲這個矩陣並不能把全部的數據包含進去。比如5就不在裏面,所以遍歷一下那些數據不在,優先用這些小的數據當做左上角繼續構造矩陣,還是會得到答案。這些得到的答案乘起來就是最後的結果。下面做一些細節的證明:

(1)爲什麼答案是乘起來,首先可以明確一點的就是不同矩陣的集合,得到的是不一樣的,所以考慮最後的答案的時候就是乘法原理的應用。

(2)(1)中有一個假設,就是不同矩陣得到的集合是不同的,這一點其實需要證明。現在這個命題可以等價轉化爲一個問題:不同矩陣的數值都是不一樣的,互不相交,並起來是全集{1,2,3...n}.對於後一點,並起來是全集這個毋庸置疑,是一定的。但是互不相交需要證明:

考慮構造矩陣的方式:一每個較小的值的二倍,三倍填充整個矩陣。當然,填的時候需要都是小於等於n的數。假設有一個數在兩個矩陣同時出現了:這兩個矩陣的初始值是x和y,那麼重複出現的值m:

在x矩陣裏一定會被表示爲:

                                                     m=x*2^{p_1}*3^{q_1}

在y矩陣裏,會被表示爲:

                                                     m=y*2^{p_2}*3^{q_2}

這裏可以簡單地討論一下,對於x,y分別做質因數分解之後,一定會變成質因數乘積的形式,如果要想等,那麼他們的差異一定只會出現在2和3這兩個質因子的冪次方上,其他部分一定是一樣的,但是如果這兩個出現了差異,那麼在x矩陣裏,一定會有另外一個位置把這個差異覆蓋掉,這一點我不好解釋,其實就是類似於自由組合的思想。所以他倆不可能相等,那麼就說明了,各個矩陣裏面的數都是不相等的。其實看了幾份題解,都沒有提到過這一點,不知道是我多慮了還是大家都默認就是乘起來。

代碼:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mod=1e9+1;
bool vis[(int)1e6+10];
int a[20][20];
ll f[20][1<<16];
int top[20];
int n;
ll calc(int x)
{
    a[1][1]=x;
    int i;
    for(i=1;;i++)
    {
        int j;
        for(j=1;;j++)
        {
            vis[a[i][j]]=true;
            a[i][j+1]=a[i][j]*3;
            if(a[i][j+1]>n) break;
        }
        top[i]=j;
        a[i+1][1]=a[i][1]*2;
        if(a[i+1][1]>n) break;
    }
    top[i+1]=0;
    int temp=i;
    for(int i=1;i<=temp;i++)
        for(int j=0;j<(1<<top[i]);j++)
            f[i][j]=0;
    f[temp+1][0]=0;
    f[0][0]=1;
    top[0]=0;
    for(i=0;i<=temp;i++)
    {
        for(int j=0;j<(1<<(top[i]));j++)
        {
            if(f[i][j]==0) continue;
            if((j&(j<<1))) continue;
            for(int k=0;k<(1<<(top[i+1]));k++)
            {
                if(j&k) continue;
                (f[i+1][k]+=f[i][j])%=mod;
            }
        }
    }
    return f[temp+1][0];
}
int main()
{
    scanf("%d",&n);
    ll ans=1;
    for(int i=1;i<=n;i++)
        if(!vis[i])
            ans=ans*calc(i)%mod;
    printf("%lld\n",ans);
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章