题目
题目大意
《集合论与图论》这门课程有一道作业题,要求同学们求出{1, 2, 3, 4, 5}的所有满足以 下条件的子集:若 x 在该子集中,则 2x 和 3x 不能在该子集中。同学们不喜欢这种具有枚举性 质的题目,于是把它变成了以下问题:对于任意一个正整数 n≤100000,如何求出{1, 2,..., n} 的满足上述约束条件的子集的个数(只需输出对 1,000,000,001 取模的结果),现在这个问题就 交给你了。
输入格式
只有一行,其中有一个正整数 n,30%的数据满足 n≤20。
输出格式
仅包含一个正整数,表示{1, 2,..., n}有多少个满足上述约束条件的子集。
算法分析
- 这个题的算法很妙 这个题的要求是如果x在集合中那么2x 3x 不能在集合中
乍一看似乎毫无思路只能打表 但是我们仔细分析题意之后 可以发现一个表格
1 | 3 | 9 |
---|---|---|
2 | 6 | 18 |
4 | 12 | 36 |
8 | 24 | 72 |
看到这个表格能想到什么 暑假集训Day2 互不侵犯(状压dp) | ||
如果选了当前位置的数 那么上下左右都不能再选 这不就是一个可以上下左右但是不能斜着攻击的国王吗 | ||
那就很容易了 | ||
先算一下数据大小 最多有18行 11列 所以我们可以用状压dp |
- 但是这个和国王的题还是有区别的 国王要求输出最多的方案数 而该题要求输出共有多少种选法,根据乘法原理(不知道是啥的去找数学老师跪搓衣板)
所以就是将答案累乘就好了
但是直接寻找这样一个表格然后累乘就对了吗?
并不是 因为观看这样的表格 我们可以发现5并不在这个表格里面 所以我们还需要累乘多个这样的表格 - 如何累乘表格?
自己算然后暴力用一个数组记录就可以了
用mark数组来标记这个表格是否出现过 - 因此我们的算法雏形就出来了(状压过程可以参见暑假集训Day2 互不侵犯(状压dp))
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6+10,mod = 1000000001;
ll tot=1;
int bin[20],n,a[20][20],cnt[20],b[20],f[20][2048];
bool mark[maxn];
int cal(int x){
memset(b,0,sizeof(b));
a[1][1] = x;
for(int i = 2;i <= 18;++i)
if(a[i-1][1]*2 <= n)a[i][1] = a[i-1][1] * 2;
else a[i][1] = n+1;
for(int i = 1;i <= 18;++i)
for(int j = 2;j <= 11;++j)
if(a[i][j-1]*3 <= n)a[i][j] = a[i][j-1]*3;
else a[i][j] = n+1;
for(int i = 1;i <= 18;++i)
for(int j = 1;j <= 11;++j)
if(a[i][j] <= n){b[i] += bin[j-1];mark[a[i][j]] = 1;}
for(int i = 0;i <= 18;++i)
for(int j = 0;j <= b[i];++j)
f[i][j] = 0;
f[0][0] = 1;//记得初始化 不然都是0
for(int i = 0;i < 18;++i)//枚举当前行数
for(int j = 0;j <= b[i];++j)//枚举当前行的状态
if(f[i][j])//如果当前行状态已经推过 小优化大约能省10ms
for(int k = 0;k <= b[i+1];++k)//枚举下一行状态
if((j&k)==0 && ((k&(k>>1)) == 0))f[i+1][k] = (f[i][j]+f[i+1][k])%mod;//记得取模 不会的去找数学老师跪搓衣板
return f[18][0];//返回第18的值
}
int main(){
//bin[0] = 1;for(int i = 1;i <= 20;++i)bin[i] = bin[i-1]<<1;
for(int i = 0;i <= 20;++i)bin[i] = 1<<i;
scanf("%d",&n);
for(int i = 1;i <= n;++i)
if(!mark[i])tot = (tot*cal(i))%mod;
printf("%lld",tot);
return 0;
}
下面注意一些细节问题:
时间复杂度(玄学) 但是我们还是可以通过一些优化来提高我们的时间效率的(不要吐槽为啥我自己代码没优化 问就是能AC的就是好代码)
比如:可以记录当前x的最大行数 和 每一行的最大列数 后面的枚举边界会变小 (这个优化不小大约能有30ms左右)
然后就是一些比较小的优化 dp时候剪枝 初始化bin的时候不用bin[i-1]的位运算而是用1<<i(实测优化2~5ms)
谢谢观看
点个关注