2734: [HNOI2012]集合选数 - BZOJ
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}。
一开始是这样想的,不能一起选的连一条边然后在图上dp
1
2 3
4 6 9
8 12 18
但是好像不行
看了题解才知道
我们把图弄成这样(往下走是*2,往右走是*3,就变成相邻的数不能选,可以用状压dp)
1 3 9
2 6 18
4 12 36
8 24 72
............
但是要注意这个时候我们并没有把所有的数都考虑到,比如5的倍数,所以我们枚举左上角的数,然后用乘法定理
具体做法是用一个flag存这个数是否考虑过,没考虑就把他当做左上角的数做一遍
1 const 2 h=1000000001; 3 maxn=100010; 4 var 5 f:array[0..18,0..2048]of longint; 6 num:array[0..18]of longint; 7 flag:array[0..maxn]of boolean; 8 n:longint; 9 ans:int64; 10 11 function get(x:longint):int64; 12 var 13 i,j,k,s,w:longint; 14 begin 15 get:=0; 16 s:=x; 17 w:=x; 18 flag[x]:=true; 19 num[1]:=1; 20 while w*3<=n do 21 begin 22 w:=w*3; 23 flag[w]:=true; 24 inc(num[1]); 25 end; 26 for j:=0 to 1<<(num[1])-1 do 27 if j and(j<<1)=0 then f[1,j]:=1; 28 i:=1; 29 while s*2<=n do 30 begin 31 inc(i); 32 s:=s*2; 33 w:=s; 34 num[i]:=1; 35 flag[w]:=true; 36 while w*3<=n do 37 begin 38 w:=w*3; 39 flag[w]:=true; 40 inc(num[i]); 41 end; 42 for j:=0 to 1<<(num[i])-1 do 43 f[i,j]:=0; 44 for j:=0 to 1<<(num[i])-1 do 45 for k:=0 to 1<<(num[i-1])-1 do 46 if (j and(j<<1)=0) and (j and k=0) then f[i,j]:=(f[i,j]+f[i-1,k])mod h; 47 end; 48 for j:=0 to 1<<(num[i])-1 do 49 inc(get,f[i,j]); 50 end; 51 52 procedure main; 53 var 54 i:longint; 55 begin 56 read(n); 57 ans:=1; 58 for i:=1 to n do 59 if flag[i]=false then ans:=(ans*get(i))mod h; 60 writeln(ans); 61 end; 62 63 begin 64 main; 65 end.