洛谷3226 BZOJ2734 HNOI2012 集合选数 构造 状压dp

9 篇文章 0 订阅
6 篇文章 0 订阅

题目链接

题意:
给你 1 1 1 n n n n n n个数,问能分成多少种不同的集合,使得每个集合里不存在一个元素 x x x 2 x 2x 2x或者 3 x 3x 3x同时存在。

题解:
这题的核心思想是构造。我们考虑构造一个矩形,左上角的第一个元素是 1 1 1,然后对于每一行,右侧的数是左侧的数的两倍;对于每一列,下方的数是上方的数的三倍。这个矩阵样子如下:

( 1 2 4 8 3 6 12 24 9 18 36 72 27 54 108 216 ) \begin{pmatrix} 1 & 2 &4 & 8\\ 3 & 6 & 12 & 24 \\ 9 & 18 & 36 & 72 \\ 27 & 54 & 108 & 216 \\ \end{pmatrix} 139272618544123610882472216

在这个矩阵中,我们只要保证矩阵中的一个相邻元素不被选到一个集合里就可以了。我们可以用一个类似互不侵犯King的状压dp来计算这个答案,因为这个矩阵的长和宽都是 l o g log log级别的。我们发现这个矩阵并不能包含所有数,于是我们还要找其他没有出现过的数再构造矩阵。每个矩阵相互独立,答案可以根据乘法原理直接相乘。我们其实并不关心矩阵里的数具体是什么,但是我们要被用过的数标记出来,写的时候我们其实只需要知道行数和列数即可。复杂度证法据说很神奇,据 y _ i m m o r t a l y\_immortal y_immortal神仙说严谨证法要积分,不知道是不是真的。

代码:

#include <bits/stdc++.h>
using namespace std;

int n,vis[500010],sta[500010],cnt;
long long ans=1,dp[20][500010],res;
const long long mod=1000000001;
int main()
{
	scanf("%d",&n);
	for(int i=0;i<=(1<<12)-1;++i)
	{
		if((i&(i<<1))==0&&(i&(i>>1))==0)
		sta[++cnt]=i;
	}
	int cur=1;
	while(cur<=n)
	{
		if(vis[cur])
		{
			++cur;
			continue;
		}
		dp[0][1]=1;
		res=0;
		int i=1,pre=1;
		while(1)
		{
			int x=cur*(1<<(i-1));
			if(x>n)
			break;
			vis[x]=1;
			int len=1;
			while(x*3<=n)
			{
				vis[x*3]=1;
				++len;
				x*=3;
			}
			int qwq=0;
			while((sta[qwq+1]&((1<<len)-1))==sta[qwq+1]&&qwq+1<cnt)
			++qwq;
			for(int j=1;j<=pre;++j)
			{
				for(int k=1;k<=qwq;++k)
				{
					if((sta[j]&sta[k])==0)
					dp[i][k]=(dp[i][k]+dp[i-1][j])%mod;
				}
			}
			++i;
			pre=qwq;
		}
		for(int j=1;j<=cnt;++j)
		res=(res+dp[i-1][j])%mod;
		ans=ans*res%mod;
		for(int j=1;j<=i;++j)
		{
			for(int k=1;k<=cnt;++k)
			dp[j][k]=0;
		}
		++cur;
	}
	printf("%lld\n",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值