P3226 [HNOI2012]集合选数
不算很难的状压dp
将 \(x\) 与 \(2x\) 和 \(3x\) 连边,限制转换为求图上的独立集个数。
注意到这个图很特殊,可以发现几点性质:
-
每个子图的根均为不能被 \(2\) , \(3\) 整除的数
-
每层节点与上层节点的连边一定
-
深度为 \(\log_2n\) ,每层节点数量最多为 \(\log_2n-\log_3n \approx 13\)
直接枚举每一层和上一层的状态转移即可,复杂度不会算
#include <cstdio>
#include <cstring>
const int MAXN = 100000 , Mod = 1e9 + 1;
int Add( int x , int y ) { x += y; return x >= Mod ? x - Mod : x; }
int Mul( int x , int y ) { return 1ll * x * y % Mod; }
int n;
int siz[ 20 ] , dp[ 20 ][ 1 << 13 ];
bool Check( int S , int T ) {
for( int i = 0 ; i < 13 ; i ++ ) if( ( S >> i ) & 1 ) if( ( ( T >> i ) & 1 ) || ( ( T >> i + 1 ) & 1 ) ) return 0;
return 1;
}
void dfs( int x , int mn , int mx ) {
if( mn > n ) { dp[ x ][ 0 ] = 1; return; }
dfs( x + 1 , mn * 2 , mx * 3 );
if( x == 1 ) siz[ x ] = 1;
else {
siz[ x ] = 0;
for( int v = mn ; v <= n && v != mx ; v = v / 2 * 3 ) siz[ x ] ++;
if( n >= mx ) siz[ x ] ++;
}
for( int S = 0 ; S < 1 << siz[ x ] ; S ++ )
for( int T = 0 ; T < 1 << siz[ x + 1 ] ; T ++ )
if( Check( S , T ) ) dp[ x ][ S ] = Add( dp[ x ][ S ] , dp[ x + 1 ][ T ] );
}
int main( ) {
scanf("%d",&n);
int Ans = 1;
for( int i = 1 ; i <= n ; i ++ ) if( i % 2 != 0 && i % 3 != 0 ) {
memset( dp , 0 , sizeof( dp ) ); dfs( 1 , i , i );
Ans = Mul( Ans , Add( dp[ 1 ][ 0 ] , dp[ 1 ][ 1 ] ) );
}
printf("%d\n", Ans );
return 0;
}