title
BZOJ 2734
LUOGU 3226
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
【样例解释】
有8 个集合满足要求,分别是空集,{1},{1,4},{2},{2,3},{3},{3,4},{4}。
Source
analysis
思路非常妙啊!
来看一个矩形:
1 2 4 8 16 .....
3 6 12 24 48 .....
9 18 36 72 144 .....
. . . . . .....
. . . . . .....
然后,就变成了,有若干个这样的矩阵,求出选不相邻的数的选法数。
那么就是状压DP的常规套路了,
因为 ,所以该矩阵的行和列一定都很小,最多大概是在 。
所以我们可以考虑在这个矩阵上状压每一行,然后统计一下该矩阵可取的方案数即可。
即设 表示矩形第 行状态为 时的集合数。
可以发现这样的矩阵有很多,因为 和 没有出现在这个矩阵中。
所以在找完 为左上角的该类型矩阵后,我们只需要寻找 中的下一个没有出现在矩阵中的元素充当 左上角 ,再次计算即可。
由于不同矩阵中的元素互不影响,所以我们把所有可能的矩阵的方案数利用 乘法原理 乘在一起即可。
code
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+1;
const int maxn=20,maxs=1<<12;
template<typename T>inline void read(T &x)
{
x=0;
T f=1, ch=getchar();
while (!isdigit(ch) && ch^'-') ch=getchar();
if (ch=='-') f=-1, ch=getchar();
while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
x*=f;
}
int N,f[maxn][maxs],tot[maxn];
bool p[maxs];
inline int solve(int x)
{
memset(tot,0,sizeof(tot));
int n=1;
for (int s=x; s<=N; ++n, s<<=1)
for (int k=s; k<=N; k*=3) ++tot[n];
--n;
memset(f,0,sizeof(f));
f[0][0]=1;
for (int i=1; i<=n; ++i)
for (int j=0; j<= (1<<tot[i-1]) -1; ++j) if (p[j])
for (int k=0; k<= (1<<tot[i]) -1; ++k) if (p[k])
if (!(j&k))
f[i][k]=(f[i][k]+f[i-1][j])%mod;
int res=0;
for (int i=0; i<= (1<<tot[n]) -1; ++i) res=(res+f[n][i])%mod;
return res;
}
int main()
{
read(N);
for (int i=0; i<(1<<12); ++i)
if (!(i&(i>>1)) && !(i&(i<<1))) p[i]=true;
int ans=1;
for (int i=1; i<=N; ++i)
if ((i%2) && (i%3)) ans=(ans*1ll*solve(i))%mod;
printf("%d\n",ans);
return 0;
}
summary
思路是真的很棒,刚开始看到此题,想着怎么把这个集合数量转化为一个可以压缩状态的东西?
然后暴毙了。。
看过yyb和wzq的 后,猛然如大梦初醒,这个方法是真的挺难想到的, 是真的强!
所以说状压DP,一定是能将题目中的关系转化成棋盘类问题的或者用了他的思想,关键就是这个转化了。
接下来,就要训练这种思维能力了。