P3226 [HNOI2012]集合选数

题目描述

《集合论与图论》这门课程有一道作业题,要求同学们求出{1, 2, 3, 4, 5}的所有满足以 下条件的子集:若 x 在该子集中,则 2x 和 3x 不能在该子集中。

同学们不喜欢这种具有枚举性 质的题目,于是把它变成了以下问题:对于任意一个正整数 n<=100000,如何求出{1, 2,…, n} 的满足上述约束条件的子集的个数(只需输出对 1,000,000,001 取模的结果),现在这个问题就 交给你了。

输入格式

只有一行,其中有一个正整数 n,30%的数据满足 n<=20。

输出格式

仅包含一个正整数,表示{1, 2,…, n}有多少个满足上述约束条件 的子集。

输入输出样例

输入

4

输出

8

说明/提示

【样例解释】

有8 个集合满足要求,分别是空集,{1},{1,4},{2},{2,3},{3},{3,4},{4}。

思路(状压DP)

  1. 题目要求“若 x 在该子集中,则 2x 和 3x 不能在该子集中”,那么我们就可以构造数个矩阵。

  2. 这些矩阵中没有重复的数,且保证了它们的并集取到了从1到n

  3. 其中每个矩阵都满足对于矩阵中任意一个数,都满足它是它左边那个数的3倍,同时是上面那个数的2倍,即a[i][j]=a[i-1][j]*2=a[i][j-1]*3,如图:在这里插入图片描述

  4. 那么题目就变成了从所构造的矩阵当中取出若干个小于n的数的方法种数,就是典型的状压DP了。

  5. 由于有多个矩阵,最终答案即为各个矩阵所得到的答案之积

小细节

  1. 行间是3倍,列间是2倍,这样可以减少每行的状态数,降低时间空间复杂度。
  2. 为了保证所有的矩阵中没有重复的数,每次构造矩阵都从未被用过的数中选取最小的一个作为矩阵的a[1][1]。
  3. 为了使取出的数满足小于等于n,可以通过判断状态所取到的最高位是否大于n,若大于n,则可直接退出(状态是单增的)。
  4. 但是构造矩阵是不满足小于等于n的 数也一样要计算出,确保上述判断有效。
  5. 注意开long long能模就模!

代码

#include<cstdio>
#include<cmath>
#define int long long
#define ri register int
const int maxn=1e5+9;
const int p=1e9+1;
int a[20][15],b[20][15];
int s[maxn],lg[maxn];
int dp[20][maxn];
int flag[maxn];
int n,ans=1;
void js(){
	int cnt=0;
	for(ri i=0;i<maxn;++i) 
	{
		if(a[1][lg[i]]>n) break;
		if(!(i&(i>>1))) s[++cnt]=i;
	}
	for(ri j=1;j<=cnt;++j) dp[1][j]=1;
	for(int i=2;i<=18;++i)
	{
		if(a[i][1]>n) break;
		for(int j=1;j<=cnt;++j)
		{
			if(a[i][lg[s[j]]]>n) break;
			for(int k=1;k<=cnt;++k)
			{
				
				if(!(s[j]&s[k])) dp[i][j]=(dp[i][j]+dp[i-1][k])%p;
				if(a[i-1][lg[s[k]]]>n) break;
			}
		}
	}
	int t=0;
	for(ri i=1;i<=18;++i)
	{
		if(a[i+1][1]<=n) continue;
		for(ri j=1;j<=cnt;++j)
			if(a[i][lg[s[j]]]<=n)
			t=(t+dp[i][j])%p;
		break;
	}
	if(t) ans=ans*t%p;
	for(ri i=1;i<=18;++i)
		for(ri j=1;j<=cnt;++j) 
			dp[i][j]=s[j]=0;
}
void get_a(int I){
	for(ri i=1;i<=18;++i)
		for(ri j=1;j<=12;++j)
		{
			a[i][j]=b[i][j]*I;
			if(a[i][j]<=n) flag[a[i][j]]=1;
		}
}
void solve(){
	scanf("%lld",&n);
	b[1][1]=1;
	for(ri i=2;i<=12;++i) b[1][i]=b[1][i-1]*3;
	for(ri i=1;i<=maxn;++i) lg[i]=lg[i-1]+(1<<lg[i-1]==i);
	for(ri i=2;i<=18;++i)
		for(ri j=1;j<=12;++j)
			b[i][j]=b[i-1][j]*2;
	for(ri I=1;I<=n;++I)
	{
		if(flag[I]) continue;
		get_a(I);
		if(a[1][1]>n) break;
		js();
	}
	printf("%lld\n",ans);	
}
#undef int
int main(){
	solve();
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
根据引用\[1\]和引用\[2\]的描述,题目中的影魔拥有n个灵魂,每个灵魂有一个战斗力ki。对于任意一对灵魂对i,j (i<j),如果不存在ks (i<s<j)大于ki或者kj,则会为影魔提供p1的攻击力。另一种情况是,如果存在一个位置k,满足ki<c<kj或者kj<c<ki,则会为影魔提供p2的攻击力。其他情况下的灵魂对不会为影魔提供攻击力。 根据引用\[3\]的描述,我们可以从左到右进行枚举。对于情况1,当扫到r\[i\]时,更新l\[i\]的贡献。对于情况2.1,当扫到l\[i\]时,更新区间\[i+1,r\[i\]-1\]的贡献。对于情况2.2,当扫到r\[i\]时,更新区间\[l\[i\]+1,i-1\]的贡献。 因此,对于给定的区间\[l,r\],我们可以根据上述方法计算出区间内所有下标二元组i,j (l<=i<j<=r)的贡献之和。 #### 引用[.reference_title] - *1* *3* [P3722 [AH2017/HNOI2017]影魔(树状数组)](https://blog.csdn.net/li_wen_zhuo/article/details/115446022)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [洛谷3722 AH2017/HNOI2017 影魔 线段树 单调栈](https://blog.csdn.net/forever_shi/article/details/119649910)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Robin_w2321

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值