题目

[HNOI2012]集合选数

《集合论与图论》这门课程有一道作业题,要求同学们求出{1, 2, 3, 4, 5}的所有满足以 下条件的子集:若 x 在该子集中,则 2x 和 3x 不能在该子集中。同学们不喜欢这种具有枚举性 质的题目,于是把它变成了以下问题:对于任意一个正整数 n≤100000,如何求出{1, 2,..., n} 的满足上述约束条件的子集的个数(只需输出对 1,000,000,001 取模的结果),现在这个问题就 交给你了。

输入格式

只有一行,其中有一个正整数 n,30%的数据满足 n≤20。

输出格式

仅包含一个正整数,表示{1, 2,..., n}有多少个满足上述约束条件 的子集。

样例

样例输入

4

样例输出

8

样例解释

有8 个集合满足要求,分别是空集,{1},{1,4},{2},{2,3},{3},{3,4},{4}。

上思路

  • 这道题真的很恶心,卡了我很长时间,在做题之前,我们需要有一个转变,x存在则2x,3x不能存在,我们建立一个二维矩阵,如下图所示
x 3x 9x 27x
2x 6x 18x 54x
4x 12x 36x 108x

由图中我们可以看出我们选出其中一个数,他的上下左右四个数都不能再选,这让我想到了炮兵营地这道题,但是还是有一定的不同,这里是让我们输出方案数,那道题是用来输出最优解的,不提炮兵营地,单讲这道题;

  • 我们可以通过构建不同的矩阵来求解最优,对于矩阵的处理,我们对已经处理过的矩阵不再重复处理,我们用矩阵的第一个数来识别矩阵是否出现过,维护\(flag\)数组,如果当前矩阵的第一个数已经出现在其他矩阵,则\(flag\)为\(1\),不需要再进行多余处理,那么如何判断该数是否出现过呢?很简单,在构图的时候顺便将出现过的数字的\(flag\)记录为\(1\),为什么要这样做?我们完全可以直接构建第一位为\(1\)的矩阵啊,但是,仔细观察,不难发现我们前面的那个矩阵有些数字是不包括的,比如\(5\),所以这个判断是完全有用的;
  • 对于一个未出现过的数字,我们以它为第一个数构建二维矩阵,在构建时应记录最大边界,如果大于\(n\),则直接跳出,为什么说是最大边界呢?因为显然我们每一行的开始数字不同,跳出时自然不同,假设以当前数构建的矩阵行数为\(m\),最大列数为\(o\),那么我们就可以在一个较小的范围内寻找每一行的边界,我们维护\(limit\)数组来记录边界状态,边界内用\(1\)表示,边界外用\(0\)表示,则构成了一个二进制数计入数组;
  • 初始化,我们先预处理第一行在合法状态下的方案数即\(f[1][0~(1<<0)-1]=1\),即为第一行合法状态的方案数都为\(1\)(显然),合法状态必然满足\((!(i&(i<<1)) && !(i&(i>>1)) && !(limit[1]&i))\),左右不冲突,不能越过当前行(即第一行)边界;
  • \(DP\)时间到了,我们定义DP数组\(f[i][j]\)表示第\(i\)行\(j\)状态的的方案数,首先,我们枚举行数(因为第一行已经处理,故从第二行开始),然后枚举当前状态,判断当前状态合法与否,合法则继续枚举可以继续枚举上一状态,即\(i-1\)行状态,判断\(i-1\)行状态是否合法,判断\(i-1\)行与\(i\)行状态是否冲突,满足情况的话我们就可以转移了,\(f[i][j]=(f[i][j]+f[i-1][k])%mod\),将上一状态叠加到当前状态;
  • 最后,还需要将第m行中所有合法状态枚举一遍,叠加到\(sum\)中即可(记得取模);
  • 还有一点考虑到时间复杂度,不要直接\(memset\)把整个数组初始化,会被卡掉(痛的教训),我们只需要在需要的位置将相应的数据变为\(0\)即可(尤其是\(f\)数组和\(limit\)数组),否则可能会\(TLE\)。

接下来就是代码了

#include<cstdio>
#include<iostream>
#include<cstring>
#define ll long long
using namespace std;
const int maxn=1e5+10;
const int mod=1e9+1;
ll f[25][1<<20],ans;
bool flag[maxn];
int n,map[25][25];
inline ll get(int x){//处理
int limit[20];//记录边界
map[1][1]=x;//记录初始点
int m=1,o=1;
for(int i=1;i<=20;i++){
if(i>1){
if(map[i-1][1]*2>n)break;//超过n,跳出
map[i][1]=map[i-1][1]*2;
flag[map[i][1]]=1;//标记
}
m=i;
for(int j=2;j<=20;j++){
if(map[i][j-1]*3>n) break;//超过n,跳出
o=max(o,j);//求最大列数
map[i][j]=map[i][j-1]*3;
flag[map[i][j]]=1;//标记
}
}
int yy=0;//记录该行个数
for(int i=1;i<=m;i++){
for(int j=1;j<=o;j++){
if(!map[i][j])break;
else map[i][j]=0;
yy=j;
}
limit[i]=0;
for(int j=yy+1;j<=o;j++)
limit[i]|=(1<<(o-j));//修改该行边界状态
}
for(int i=0;i<(1<<o);i++)//初始化
if(!(i&(i<<1)) && !(i&(i>>1)) && !(limit[1]&i))
f[1][i]=1;
for(int i=2;i<=m;i++)//枚举行数
for(int j=0;j<(1<<o);j++)//枚举当前行状态
if(!(j&(j<<1)) && !(j&(j>>1)) && !(limit[i]&j)){//判断合法与否
f[i][j]=0;//初始化,优化!!!!
for(int k=0;k<(1<<o);k++)//枚举上一状态
if(!(k&(k<<1)) && !(k&(k>>1)) && !(limit[i-1]&k) && !(j&k))//判断上一状态合法与否,上一状态和现状态是否冲突
f[i][j]=(f[i][j]+f[i-1][k])%mod;//叠加上一状态方案数
}
int sum=0;
for(int i=0;i<(1<<o);i++){//求第m行时,所有状态的最优解的和
if(!(i&(i<<1)) && !(i&(i>>1)) && !(i&limit[m])){
sum=(sum+f[m][i])%mod;
}
}
return sum;
}
int main(){
scanf("%d",&n);
ans=1;
for(int i=1;i<=n;i++)
if(!flag[i])//判断是否被标记
ans=(ans*get(i))%mod;
printf("%lld\n",ans);
}

状压DP之集合选数的更多相关文章

  1. [状压dp]HDOJ1565 方格取数(1)

    中文题~~ 题意略 $n\le 20$ ! 很明显是状压! #include <cstdio> #include <cstdlib> #include <cstring& ...

  2. HDU 6984 - Tree Planting(数据分治+状压 dp)

    题面传送门 傻逼卡常屑题/bs/bs,大概现场过得人比较少的原因就是它比较卡常罢(Fog 首先对于这样的题我们很难直接维护,不过注意到这个 \(n=300\) 给得很灵性,\(k\) 比较小和 \(k ...

  3. [BZOJ2734][HNOI2012] 集合选数(状态压缩+思维)

    Description 题目链接 Solution 可以根据条件构造出一个矩阵, 1 3 9 27 81... 2 6 18.... 4 12 36... 这个矩阵满足\(G[i][1]=G[i-1] ...

  4. 【BZOJ-2732】集合选数 状压DP (思路题)

    2734: [HNOI2012]集合选数 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1070  Solved: 623[Submit][Statu ...

  5. BZOJ_2734_[HNOI2012]集合选数_构造+状压DP

    BZOJ_2734_[HNOI2012]集合选数_构造+状压DP 题意:<集合论与图论>这门课程有一道作业题,要求同学们求出{1, 2, 3, 4, 5}的所有满足以 下条件的子集:若 x ...

  6. [HNOI2012]集合选数 --- 状压DP

    [HNOI2012]集合选数 题目描述 <集合论与图论>这门课程有一道作业题,要求同学们求出\({1,2,3,4,5}\)的所有满足以 下条件的子集:若 x 在该子集中,则 2x 和 3x ...

  7. 【BZOJ-2734】集合选数 状压DP (思路题)

    2734: [HNOI2012]集合选数 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1070  Solved: 623[Submit][Statu ...

  8. BZOJ 2734: [HNOI2012]集合选数 [DP 状压 转化]

    传送门 题意:对于任意一个正整数 n≤100000,如何求出{1, 2,..., n} 的满足若 x 在该子集中,则 2x 和 3x 不能在该子集中的子集的个数(只需输出对 1,000,000,001 ...

  9. [HNOI2012]集合选数(状压DP+构造)

    题目要求若出现x,则不能出现2x,3x 所以我们考虑构造一个矩阵 \(1\ 2\ 4 \ 8--\) \(3\ 6\ 12\ 24--\) \(9\ 18\ 36--\) \(--\) 不难发现,对于 ...

随机推荐

  1. 调优 | Apache Hudi应用调优指南

    通过Spark作业将数据写入Hudi时,Spark应用的调优技巧也适用于此.如果要提高性能或可靠性,请牢记以下几点. 输入并行性:Hudi对输入进行分区默认并发度为1500,以确保每个Spark分区都 ...

  2. Centos网络配置文件详解

    配置文件位置: 根目录下面的etc下面的sysconfig下面的network-scripts下面的网卡名称配置文件. /etc/sysconfig/network-scripts/网卡名称 如图:我 ...

  3. Koa源码解析,带你实现一个迷你版的Koa

    前言 本文是我在阅读 Koa 源码后,并实现迷你版 Koa 的过程.如果你使用过 Koa 但不知道内部的原理,我想这篇文章应该能够帮助到你,实现一个迷你版的 Koa 不会很难. 本文会循序渐进的解析内 ...

  4. VMWare 安装CentOS7 时启动黑屏

    针对这个问题找了好久解决方案,发现网络上的都没啥用. 首先根据网络上的文章,查看cpu虚拟化设置.清空网络设置等等... 都没什么效果. 经过一段时间排查发现问题根源: win10系统下,启动 vmw ...

  5. EIGRP-10-弥散更新算法-计算距离,报告距离,可行距离和可行性条件

    对于某个目的网络,EIGRP持续关注它的各种距离参数.EIGRP使用复合度量参数,不过为了简化,这里使用一个没有单位的数值.同样出于简化,这里的EIGRP路由器都不使用水平分割.

  6. position中的四种属性

    Position有四个属性值,分别是static .fixed. relative .absolute. 第一个属性值是static,这是position的默认属性,一般我们都不会用到它,所以也很少提 ...

  7. Day7-微信小程序实战-天气预报小程序

    前段时间在B站跟着一个视频,搞天气预报小程序 https://www.bilibili.com/video/BV1cJ411879s 但是因为这个调用的接口要money,太贵了就没买,就只是做了一些不 ...

  8. Redis删除策略和逐出策略

    本文知识点 过期数据概念 数据删除策略 逐出算法 过期数据 先来看三个key值,分别为sex.name.age. 这三个值设置的指令为 set name kaka setex age 100 24 s ...

  9. 3.kubernetes的CNI网络插件-Flannel

    目录 1.1.K8S的CNI网络插件-Flannel 1.1.1.集群规划 1.1.2.下载软件.解压.软链接 1.1.3.最终目录结构 1.1.4.拷贝证书 1.1.5.创建配置 1.1.6.创建启 ...

  10. Bestcoder Round8

    4989Summary 既然用C++了就偷懒直接用STL大法了 #include<iostream> #include<algorithm> #include<vecto ...