【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

 

posted @ 2018-12-17 20:43  QuartZ_Z  阅读(305)  评论(1编辑  收藏  举报