BZOJ 2734 [HNOI2012]集合选数 状压+思路

前言:

总觉得几天没写博客了。
感觉自己被sb题以及sb错误包围了…
那今天就精挑细选几个题写写博客吧。


题意:

在{1,2,3,…..,n}的集合中选出一个子集。
该子集满足一条约束条件:若x在该集合中,那么2*x以及3*x不能在这个集合中。
询问能选出的子集个数对1000000001取模的结果。


解析:

这题的思路蛮有趣的。
我们不妨写出来一个矩阵。

行\列12345
11392781
2261854162
341236108324
482472216648
516481444321296

该矩阵的每一个元素的右面的元素都是他的3倍,每一个元素的下面的元素都是他的2倍。
也就是说,如果我们选出的数在该矩阵中是不相邻的话,那么选出的一定是一个符合题意的子集。
因为 n100000 ,所以该矩阵的行和列一定都很小,最多大概是在17。
所以我们可以考虑在这个矩阵上状压每一行,然后统计一下该矩阵可取的方案数即可。
但是我们发现,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);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值