2734: [HNOI2012]集合选数
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1475 Solved: 876
[ Submit][ Status][ Discuss]
Description
《集合论与图论》这门课程有一道作业题,要求同学们求出{1, 2, 3, 4, 5}的所有满足以 下条件的子集:若 x 在该子集中,则 2x 和 3x 不能在该子集中。同学们不喜欢这种具有枚举性 质的题目,于是把它变成了以下问题:对于任意一个正整数 n≤100000,如何求出{1, 2,..., n} 的满足上述约束条件的子集的个数(只需输出对 1,000,000,001 取模的结果),现在这个问题就 交给你了。
Input
只有一行,其中有一个正整数 n,30%的数据满足 n≤20。
Output
仅包含一个正整数,表示{1, 2,..., n}有多少个满足上述约束条件 的子集。
Sample Input
4
Sample Output
【样例解释】
有8 个集合满足要求,分别是空集,{1},{1,4},{2},{2,3},{3},{3,4},{4}。
HINT
Source
一开始有一个朴素的思路就是dp[i][0/1]表示当前选还是不选的方案数, 然后减去dp[i/2][1]和dp[i/3][1]的, 发现可能会删重就需要记录一些其他的东西.... 发现处理不好gg. 于是往根号方面的复杂度去想也没有什么卵用. 看了一下30%的数据感觉暴力状压可过. 然后实在想不出来, orz了题解.
解题思路真的有点神... 总结一下思路. 我们可以看到题目中只有2x, 3x这样的限制可以产生一点疑惑:为什么不能再是3x, 4x... 而是只有两个限制? 原来我认为无非就是一种可能:如果是dp的话dp状态会减少. 但是做了一下发现很难去除重复的情况, 选择gg. 那还有什么可能呢? 实际上在某些序列上的数据结构问题我们会发现可以将限制问题转化为几维的限制, 比如BZOJ3489, 可以化成三维限制关系kd-tree水过(虽然我写的树套树...). 那么这道题同理, 只给出了两位限制我们就往两维想. 把2的倍数设为一维, 3的倍数设为一维. 那么比如说我们可以得到这么一个矩阵(二维平面).
1 3 9 27 81....
2 6 18 54 162...
4 12 36 108 324...
横着3倍关系, 竖着2倍关系. 我们会发现题目中要求的限制就是不能选相邻的数!很容易发现矩阵横(列)不超过11, 竖(行)不超过17, 那么一个很naive的状压就完成了. 我们同时会发现这个矩阵没有5, 7... 5的倍数, 7的倍数,没关系我们可以再构建出形如这样的矩阵:
5 15 45...
10 30 90...
20 60 120...
这样每个矩阵状压得出的答案相乘就是结果. 这相当于从不同的矩阵里选数, 因为每个矩阵数字不重复, 显然可以运用乘法原理. 关于时间复杂度我也不会证明... 可以感性的想虽然有多个矩阵会不可过, 但实际上我们知道不是2的倍数又不是3的倍数就会被作为一个矩阵的左上角. 这样的数虽然也不少但是当数变大的时候这个矩阵也会迅速的变小(因为横着乘2次幂, 竖着乘3次幂), 状压状态数会迅速变的非常少, 所以说感觉可过QAQ... 测了极限数据快的飞起. 这样的复杂度如果较难分析实际上可以通过打表算来看是不是可过.
所以总而言之, dp中题目限制屈指可数的话(因为越高维空间时间都开销inf啊), 除了往状态数, 枚举两方面想, 实际上还可以往维度方面想, 构造图形来dp. 这道题如果是2x,3x, 5x不可以的话, 按照之前的想法不难分析这就会是一个立方体.
#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 1;
int n, tmp, ans;
bool mark[1 << 18];
int pw[19], line[19], f[19][1 << 13], a[19][13];
inline int calc(const int &x) {
a[1][1] = x;
register int i, j;
for (i = 1; ; ++ i) {
if (i ^ 1) {
a[i][1] = a[i - 1][1] << 1;//
if (a[i][1] > n) {
tmp = i - 1;
break;
}
}
mark[a[i][1]] = true;
for (j = 2; ; ++ j) {
a[i][j] = (a[i][j - 1] << 1) + a[i][j - 1];
if (a[i][j] > n) {
line[i] = j - 1;
break;
}
mark[a[i][j]] = 1;
}
}
for (i = 0; i <= tmp; ++ i)
for (j = 0; j < pw[line[i]]; ++ j)
f[i][j] = 0;
line[0] = 1, line[tmp + 1] = 0;
f[0][0] = 1, f[tmp + 1][0] = 0;
for (i = 0; i <= tmp; ++ i)
for (j = 0; j < pw[line[i]]; ++ j)
if (f[i][j] && !(j & (j >> 1)))
for (int k = 0; k < pw[line[i + 1]]; ++ k)
if (!(j & k)) f[i + 1][k] = (f[i + 1][k] + f[i][j]) % mod;
return f[tmp + 1][0];
}
int main() {
register int i;
ans = pw[0] = 1;
scanf("%d", &n);
for (i = 1; i < 19; ++ i)
pw[i] = pw[i - 1] << 1;
for (i = 1; i <= n; ++ i)
if (!mark[i]) ans = 1ll * ans * calc(i) % mod;
printf("%d\n", ans);
}