【[HNOI2012]集合选数】数学构造+状态压缩

在和xiaoaoao交流之下其实很早就想到了正解,,然而在一些地方纠缠不清加有点怂一直没敢写出来,为此又颓废了一天。orz BZOJ2734非权限题    Luogu3226  

2734: [HNOI2012]集合选数

Time Limit: 10 Sec  Memory Limit: 128 MB Submit: 1624  Solved: 964 [Submit][Status][Discuss]

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}。
我们考虑将这些数分化,由于每个数x是否可选只与 (x/2) (x/3) (x*2) (x*3) 有关,于是我们考虑到这个可以构造出一个矩阵
【1*x】【3*x】【9*x】 【27*x】 。。。。。
【2*x】【6*x】 【18*x】 .......
【4*x】【12*x】 【36*x】.....
【8*x】......
.....
也就是矩阵的四周分别是我们提到的那些有关数,这样问题就转化为了在矩阵(事实上由于有n的限制其应该为一个锯齿状)里面选取不相邻元素的方案数。
我们可以显然可见,一直乘3,长度不会超过11,宽度也不会太多。我们具体解决的时候运用类似炮兵阵地Luogu炮兵阵地的思路搞一个DP或者记忆化搜索(事实上代码能力很弱的我只写得出记忆化)就可以了。由于对于一个矩阵其会有许多数没有包含进去。而对于这些没有包含的,我们对其一个一个建立矩阵然后DP。
由于这些彼此矩阵之间的数不会重复,也即恰好n个数建立完所有矩阵。而彼此矩阵之间不会有联系(我这边选了方案对方矩阵的方案数不受影响)我们直接乘法原理将所有矩阵方案数相乘即可。
稍微卡卡常也就过去了(当这一排的某个数超过n,这一排接下来的数就不需要构造了)
code:
//%%%
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const ll mod = 1000000001LL;
ll n,S;
ll dp[20][1<<12];
ll jz[20][20];
bool mark[20][1<<12];
ll pos[20];
ll ans=1;
bool bb[100005];
bool check(ll now,ll last,ll pai)
{
	if( ( !(now&last) ) && ( (pos[pai]|now) == pos[pai] ) && ( !(now&(now>>1)) ) )   return true;
	else return false;
}
ll dfs(ll &limit,ll pai,ll now)
{
	if(mark[pai][now]) return dp[pai][now]%mod;
	mark[pai][now]=1;
	if(pai==limit) return dp[pai][now]=1;
	ll sum=0; 
	for(ll i=S;i>=0;i--)
	{
		if(check(i,now,pai+1))
		{
			sum = (sum + dfs(limit,pai+1,i))%mod;
		}
	}
	return dp[pai][now] = sum;
}
void gzdp(ll x)
{
	memset(pos,0,sizeof pos);
	ll chang=x,cc=1,kuan=x,kk=1;
	while(chang*3<=n) ++cc,chang*=3;
	while(kuan*2<=n) ++kk,kuan*=2;
	jz[1][1]=x; bb[x]=1;
	for(int i=2;i<=cc;i++) jz[1][i]=jz[1][i-1]*3,bb[jz[1][i]]=1;
	for(int i=2;i<=kk;i++)
	{
		jz[i][1]=jz[i-1][1]*2; bb[jz[i][1]]=1; pos[i]|=1;
		for(int j=2;j<=cc;j++)
		{
			jz[i][j]=jz[i][j-1]*3;
			if(jz[i][j]>n) break;
			else 
			{
				bb[jz[i][j]]=1;
				pos[i] |= (1<<(j-1));
			}
		}
	}
	S=(1<<cc)-1; pos[1]=S;
	ll sum = 0;
	for(int s=0;s<=S;s++)
	{
		if(check(s,0,1)) sum = ( sum + dfs( kk , 1,s ) )%mod;
	}
	ans = ans * sum%mod;
	for(ll i=1;i<=kk;i++)
	{
		for(ll j=0;j<=S;j++)
		{
			dp[i][j]=0;
			mark[i][j]=0;
		}
	}
}
int main()
{
	scanf("%lld",&n);
	for(ll i=1;i<=n;i++)
	{
		if(!bb[i]) gzdp(i);
	}
	printf("%lld",ans);
}
 
posted @ 2018-05-30 17:13  Newuser233  阅读(8)  评论(0)    收藏  举报