1. 程式人生 > >【bzoj2734】集合選數(有點思維的狀壓dp)

【bzoj2734】集合選數(有點思維的狀壓dp)

  題目傳送門:bzoj2734

  這題一個月前看的時候沒什麼頭緒。現在一看,其實超簡單。

  我們對於每個在$ [1,n] $範圍內的,沒有因數2和3的數$ d $,將它的倍數$ 2^a 3^b d $一起處理。因為每個數$ d $之間沒有2和3作為公因數,所以統計時互不影響。

  對於$ d $的倍數$ 2^a 3^b d $,我們可以發現如果把它按因子2的次數為行,因子3的次數為列,把這些數排列在一個矩形中,相當於是在一個階梯狀的棋盤上選擇最多的互不相鄰的格子。這個可以用狀壓dp計算。

  其實這題的主要難度在於複雜度的分析,我一個月前也是沒算出複雜度然後主觀否決了這個方案。

  於是我們現在來分析一下時間複雜度:

    對於數$ d $,將其倍數$ 2^a 3^b $排列成的矩形的規模是$ \log_2(\frac{n}{d}) \times \log_3(\frac{n}{d}) $的,而對於一個$ n \times m $的矩形進行狀壓dp選擇最多的互補相鄰的格子的時間複雜度為$ O(2.618^mn) $(因為可以預處理出每一行的所有滿足選擇的格子互不相鄰的有效狀態,而有效狀態的數量是$ O(1.618^m) $的,所以綜合起來複雜度就是$ O(2.618^mn) $)。因此,處理數d時所花費的時間複雜度為$ O(\frac{n}{d} \log(\frac{n}{d})) $。

    因此,總時間複雜度為:$ \sum_{d=1}^{n}\frac{n}{d} \log(\frac{n}{d}) = n \log^2 n $

  程式碼:

#include<cstdio>
#include<cmath>
#define ll long long
#define mod 1000000001
#define maxn 100010
int vis[maxn],can[20][1<<15],st[310];
ll a[20][20],f[20][310];
int n;
int work(int x)
{
    int w=(int)(log(n/x)/log(3)+1e-10)+1,h=(int)(log(n/x)/log(2)+1e-10)+1,tot=0;
    a[1][1
]=x; for(int i=2;i<=w;i++) a[1][i]=a[1][i-1]*3; for(int i=2;i<=h;i++) for(int j=1;j<=w;j++) a[i][j]=a[i-1][j]*2; for(int i=1;i<=h;i++) for(int j=1;j<=w;j++) if(a[i][j]<=n)vis[a[i][j]]=1; for(int i=0;i<=h;i++) for(int j=0;j<1<<w;j++){ int flag=1; for(int k=0;k<w;k++) if((j&(1<<k))&&a[i][k+1]>n){ flag=0; break; } if(flag)can[i][j]=1; else can[i][j]=0; } for(int i=0;i<1<<w;i++) if(!(i&(i<<1))&&!(i&(i>>1)))st[++tot]=i; f[0][1]=1; for(int i=1;i<=h;i++) for(int j=1;j<=tot;j++){ f[i][j]=0; for(int k=1;k<=tot;k++) if(can[i][st[j]]&&can[i-1][st[k]]&&!(st[j]&st[k])){ f[i][j]+=f[i-1][k]; if(f[i][j]>=mod)f[i][j]-=mod; } } int ans=0; for(int i=1;i<=tot;i++) if(can[h][st[i]]){ ans+=f[h][i]; if(ans>=mod)ans-=mod; } return ans; } int main() { scanf("%d",&n); ll ans=1; for(int i=1;i<=n;i++) if(!vis[i])ans=ans*work(i)%mod; printf("%lld\n",ans); }
bzoj2734