【洛谷】3226 [HNOI2012] 集合选数

1 篇文章 0 订阅

哇很好很好的 Dp 题目

YYR 讲了这道题之后我瞬间就忘记啦,所以只能自己想了

大概思路还是记得哒

 

很明显这题就是不能让你去暴力枚举的,阶乘级别的复杂度啊

但是怎么去 Dp 呢,单纯的根据题意去尝试定义 Dp 方程似乎并不是很容易定义,这个时候需要尝试构造模型:

 

选了一个元素就不能选另一个元素或者另外某些元素的模型是什么呢?

是不是有树形 Dp 类似的题目:【没有上司的舞会】,选了上司就不能选择员工啊

但是无法将这个题目抽象成树形 Dp,因为一个合法的小于 N 的数 X 有两个爸爸:X/2 和 X/3,所以无法抽象成树规!

 

还有什么模型呢?

洛谷的【互不侵犯 King】,放置了一个 King 之后其周围的 8 个格子都不能放置 King 了,那么我们如果构造一个矩阵,横向相邻的数的商为3,纵向的商为2,如下:

1    3     9   ……

2    6    18  ……

4   12   36  ……

...   ...   ...

这样就和【互不侵犯 King】大相径庭……呸,大同小异啦(Splay那篇文章的梗哈哈哈哈):选择了一个数就不能选择上下左右相邻的数啦!

进一步发现,每一行最多有 log _{3}^{10000}\approx 11 列啦,这就很很很明显了,状压!

定义 Dp 方程 dp [ i ] [ { 当前行所有合法状态 } ] += dp [ i - 1 ] [ { 所有合法的上一行状态 } ]

一个状态是合法的当且仅当它两两相邻的位 & 值为 0!上下 & 的值为 1!

 

但是这样的话有些数是不会出现在上图的矩阵中的,哪些数呢?

当然是同时与 2、3 互质的数啦!

我们只需要预处理出所有与 2、3 互质的所有数,然后将每一个作为矩阵的左上角构造就好啦!

 

AC代码:

 

# include <bits/stdc++.h>

const  int  N = 23 , M = 17 , mod = 1000000001 ;

long long  dp [ N ] [ ( 1 << M ) + 5 ] , to [ ( 1 << M ) + 5 ] ;
int  tab [ ( 1 << M ) + 5 ] , num [ N ] , cnt [ N ] , sta [ ( 1 << M ) + 5 ] , buck [ ( 1 << M ) + 5 ] , tp [ ( 1 << M ) + 5 ];
int  n , row , col , tot ;
long long  Gyx = 1 ;

void  init ( ) {
	for ( int  s1 = 0 ; s1 < ( 1 << M ) ; s1 ++ ) {
		int  tmp = s1 , dig = 0 ;
		while ( tmp ) {
			dig ++ ;
			tmp >>= 1 ;
		}
		if ( ( s1  &  ( s1 >> 1 ) )  ||  ( ( s1  &  ( s1 << 1 ) ) ) ) {
			to [ s1 ] = tot ;
			buck [ s1 ] = dig ; 
			continue ;
		}
		sta [ ++ tot ] = s1 ;
		to [ s1 ] = tot ;
		buck [ s1 ] = dig ;
	}
	for ( int  i = 1 ; i <= 11 ; i ++ ) {
		int  s = ( 1 << i ) - 1 ;
		num [ buck [ s ] ] = to [ s ] ;
	}
}

void  get_tab ( ) {
	for ( int  i = 1 ; i <= n ; i ++ )
		if ( ( i % 2 )  &&  ( i % 3 ) )
			tab [ ++ tab [ 0 ] ] = i ;
}

void  construct ( int  x ) {
	row = col = 0 ;
	memset ( cnt , 0 , sizeof ( cnt ) ) ;
	for ( int  i = tab [ x ] ; i <= n ; i *= 2 ) {
		row ++ ;
		for ( int  j = i ; j <= n ; j *= 3 )
			cnt [ row ] ++ ;
	}
}

long long  Dp ( ) {
	for ( int  i = 1 ; i <= num [ cnt [ 1 ] ] ; i ++ )
		dp [ 1 ] [ sta [ i ] ] = 1 ;
	for ( int  i = 2 ; i <= row ; i ++ )
		for ( int  j = 1 ; j <= num [ cnt [ i ] ] ; j ++ ) {
			dp [ i ] [ sta [ j ] ] = 0 ;
			for ( int  k = 1 ; k <= num [ cnt [ i - 1 ] ] ; k ++ ) 
				if ( ! ( sta [ k ]  &  sta [ j ] ) ) {
					dp [ i ] [ sta [ j ] ] = ( dp [ i ] [ sta [ j ] ] + dp [ i - 1 ] [ sta [ k ] ] ) % mod ;
				}
		}
	long long  ans = 0 ;
	for ( int  i = 1 ; i <= num [ cnt [ row ] ] ; i ++ )
		ans = ( ans + dp [ row ] [ sta [ i ] ] ) % mod ;
	return  ans ;
}

int  main ( ) {
	scanf ( "%d" , & n ) ;

	get_tab ( ) ;
	init ( ) ;

	for ( int  mmy = 1 ; mmy <= tab [ 0 ] ; mmy ++ ) {
		construct ( mmy ) ;
		int  tmp = Dp ( ) ;
		Gyx = ( int ) ( ( 1ll * Gyx * tmp ) % mod ) ; 
	}
	printf ( "%lld" , Gyx ) ;
	return  0 ;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值