前言:
總覺得幾天沒寫博客了。
感覺自己被sb題以及sb錯誤包圍了…
那今天就精挑細選幾個題寫寫博客吧。
題意:
在{1,2,3,…..,n}的集合中選出一個子集。
該子集滿足一條約束條件:若x在該集合中,那麼2*x以及3*x不能在這個集合中。
詢問能選出的子集個數對1000000001取模的結果。
解析:
這題的思路蠻有趣的。
我們不妨寫出來一個矩陣。
行\列 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
1 | 1 | 3 | 9 | 27 | 81 |
2 | 2 | 6 | 18 | 54 | 162 |
3 | 4 | 12 | 36 | 108 | 324 |
4 | 8 | 24 | 72 | 216 | 648 |
5 | 16 | 48 | 144 | 432 | 1296 |
該矩陣的每一個元素的右面的元素都是他的3倍,每一個元素的下面的元素都是他的2倍。
也就是說,如果我們選出的數在該矩陣中是不相鄰的話,那麼選出的一定是一個符合題意的子集。
因爲
所以我們可以考慮在這個矩陣上狀壓每一行,然後統計一下該矩陣可取的方案數即可。
但是我們發現,5,7這種數並沒有出現在該矩陣中。
所以這種矩陣可能有多個,在找完1爲左上角的該類型矩陣後,我們只需要尋找1~n中的下一個沒有出現在矩陣中的元素充當左上角,再次計算即可。
由於不同矩陣中的元素互不影響,所以我們需要把所有可能的矩陣的方案數利用乘法原理乘在一起即可。
後記:
當時好像還腦抽想了一下是不是隻有質數能夠充當矩陣的左上角,然而並不是這樣,比如你想一想49就明白了。
代碼:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mod 1000000001
#define N 21
using namespace std;
typedef long long ll;
int n;
int a[N][N];
ll f[N][70010];
int vis[100100];
int line[N];
ll calc(int x)
{
a[1][1]=x;
int tmp;
for(int i=1;;i++)
{
if(i!=1)
{
a[i][1]=a[i-1][1]*2;
if(a[i][1]>n)
{tmp=i-1;break;}
}
vis[a[i][1]]=1;
for(int j=2;;j++)
{
a[i][j]=a[i][j-1]*3;
if(a[i][j]>n)
{line[i]=j-1;break;}
vis[a[i][j]]=1;
}
}
line[0]=1;
for(int i=0;i<=tmp;i++)
for(int j=0;j<(1<<line[i]);j++)
f[i][j]=0;
line[tmp+1]=0,f[tmp+1][0]=0;
f[0][0]=1;
for(int i=0;i<=tmp;i++)
{
for(int j=0;j<(1<<line[i]);j++)
{
if(f[i][j])
{
if(j&(j>>1))continue;
for(int k=0;k<(1<<line[i+1]);k++)
{
if(j&k)continue;
f[i+1][k]=(f[i+1][k]+f[i][j])%mod;
}
}
}
}
return f[tmp+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);
}