P3226 [HNOI2012]集合选数

不算很难的状压dp

\(x\)\(2x\)\(3x\) 连边,限制转换为求图上的独立集个数。

注意到这个图很特殊,可以发现几点性质:

graph.png

  • 每个子图的根均为不能被 \(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;
}
posted @ 2021-10-14 21:27  chihik  阅读(23)  评论(0编辑  收藏  举报