首页 > 其他分享 >[HNOI2012] 集合选数

[HNOI2012] 集合选数

时间:2023-07-18 09:33:26浏览次数:28  
标签:状态 le int 矩阵 样例 HNOI2012 选数 集合 line

[HNOI2012] 集合选数

目录

题目描述

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

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

输入格式

只有一行,其中有一个正整数 \(n\)。\(30 \%\) 的数据满足 \(n \le 20\)。

输出格式

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

样例 #1

样例输入 #1

4

样例输出 #1

8

提示

【样例解释】

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

【数据范围】

对于 \(30 \%\) 的数据,\(n \le 20\)。
对于 \(100 \%\) 的数据,\(1 \le n \le 10^5\)。

题意概括

就是让你在\(1-n\)里面选数,要求选了\(x\)就不能选\(2x,3x\),求最大方案数。

思路历程:

私货:当代互联网天下第一!!!

1.设计状态

显然我们应该设计是否选择\(x\)为状态,而是否满足条件在转移前判断。

满足条件是不满足条件的对立事件,所以我们可以先把不能在一起的点做出来,这样我们就把问题转化为了不能选择矩阵中相邻的点:

以\(1\)举例子,\(n=100\):

1  2  4  8   16  32  64
3  6  12 24  48  96
9  18 36 72
27 54
81

可见矩阵横着是乘\(2\),竖着是乘\(3\),均小于\(n=100\)

但是我们这个显然是不全的啊,要满足条件又不止满足\(1\)。

我们可以发现,这个矩阵里面,\(2\),\(3\)这样的\(1*2\)的倍数或\(1*3\)的倍数的数已经选入,不用再管,但是像\(5\)这样的数,我们还需要继续再列一个矩阵。

因此我们用一个特判数组,只要矩阵里出现过这个点就是\(true\),否则再列一个矩阵。

matrix pre
inline void pre(int x){
	for(int i=1;i<=lg3n;++i){
		if(i==1)	a[i][1]=x;
		else	a[i][1]=a[i-1][1]*3;
		if(a[i][1]>n)	break;
		yend=i,line[i]=1,judge[a[i][1]]=true;
		for(int j=2;j<=lg3n;++j){
			a[i][j]=a[i][j-1]<<1;
			if(a[i][j]>n)	break;
			line[i]=j;
			judge[a[i][j]]=true;
		}
		len[i]=(1<<line[i])-1;
	}
}

for(int i=1;i<=n;++i){
	if(!judge[i]){
		pre(i);
	}
}

而对于状态,我们不能选择具有在矩阵中相邻的点的状态,可以进行初始化:

pre2
for(int i=0;i<=maxn-1;++i){
		if( (i<<1) & i)	g[i]=0;
		else	g[i]=1;
	}

2.设计转移

我们的状态是以一个数扩展而成的矩阵,状态也应该是这样的。

因此我们的状态建立在以某数\(x\)构造的矩阵上,有\(f_{i,j}\)表示矩阵内\(1-i\)行中第\(i\)行选择状态\(j\)有多少种方案,显然有转移:

\[\begin{aligned} & if(上一行的状态k合法 并且 上一行的状态k与改行枚举的状态j没有重合点)\\ & f_{i,j}=f_{i,j}+f_{i-1,k} \end{aligned} \]

最后,因为我们每个矩阵是不会存在交叉部分的,比如\(1\)的矩阵里可以选出\(1,2,3\),\(5\)的矩阵里可以选出\(5,10,15\),显然我们可以选出\(1,2,3,5\),或者\(1,2,3,5,10\),不同矩阵之间的方案数是遵循乘法原则的。

代码实现:

Miku's Code
#include<bits/stdc++.h>
using namespace std;

typedef long long intx;
const int maxn=1e5+50,mod=1e9+1;
const int lg2n=18,lg3n=13;

intx n,ans,num;
intx g[(1<<lg2n)+2];
intx a[lg2n+2][lg2n+2],line[lg2n+2],len[lg2n+2],yend;
bool judge[maxn];
intx f[lg2n+2][(1<<lg2n)+2];

inline void pre(intx x){							//矩阵初始化 
	for(intx i=1;i<=lg3n;++i){					//第一列初始化 
		if(i==1)	a[i][1]=x;
		else	a[i][1]=a[i-1][1]*3;
		if(a[i][1]>n)	break;
		yend=i,line[i]=1,judge[a[i][1]]=true;	//yend表示第一列的最后一行,line表示某一行的最后一列 
		for(intx j=2;j<=lg2n;++j){
			a[i][j]=a[i][j-1]*2;
			if(a[i][j]>n)	break;
			line[i]=j,judge[a[i][j]]=true;
		}
		len[i]=(1<<line[i])-1;					//len表示某一行最大状态 
	}
}

inline intx work(){
	num=0;
	for(intx i=0;i<=len[1];++i){
		f[1][i]=g[i];
	}
	for(intx i=2;i<=yend;++i){
		for(intx j=0;j<=len[i];++j){
			if(!g[j])	continue;
			f[i][j]=0;
			//一定要记得清空啊,或者说在pre()里面memset,但是用到在清省复杂度 
			for(intx k=0;k<=len[i-1];++k){
				if( g[k] && ((k & j)==0) ){
					f[i][j]+=f[i-1][k];
					f[i][j]%=mod;
				}
			}
		}
	}
	for(intx i=0;i<=len[yend];++i){
		num+=f[yend][i];
		num%=mod;
	}
	return num;
}

int main(){
	scanf("%lld",&n);
	ans=1;
	for(intx i=0;i<=(1<<lg2n)-1;++i){
		if( (i<<1) & i)	g[i]=0;
		else	g[i]=1;
	}
	for(intx i=1;i<=n;++i){
		if(!judge[i]){
			pre(i);
			int s=work();
			ans=ans*s%mod;
		}
	}
	printf("%lld",ans);
	return 0;
}

标签:状态,le,int,矩阵,样例,HNOI2012,选数,集合,line
From: https://www.cnblogs.com/sonnety-v0cali0d-kksk/p/17561907.html

相关文章

  • 字符串,列表的内置方法(增加、修改、删除) 、可变类型与不可变类型 、字典 ,元组,集合的
    字符串的内置方法(较多,重要)old_code='KeViN'print('这是返回给用户的验证码:%s'%old_code)new_code=input('请输入你的验证码:').strip()print(new_code)#对验证码作一个判断,现在对验证码作不区分带小写#ifold_code.upper()==new_code.upper():ifold_code.......
  • 字典,元组,元组内置方法、相关面试题 、 集合的内置方法 、字符编码 、文件操作 、函数
    字典的内置方法1.定义方式 d={'usernamne':"kevin"}#定义空字典d={}info=dict(username='kevin',age=18)#{'username':'kevin','age':18} print(info) #dic={#'name':�......
  • abc310d <dfs暴搜-分组方案数 / bitmask表示集合+dp>
    题目D-PeacefulTeams参考:https://www.cnblogs.com/legendstane/p/freee-programming-contest-2023-atcoder-beginner-contest-abc-310-solution.htmlhttps://blog.csdn.net/Muelsyse_/article/details/131747083思路方法1dfs暴搜由于数据范围极小,所以直接暴力即可......
  • redis剔除集合中某值
    Redis剔除集合中某值的方法详解Redis是一个开源的内存数据结构存储系统,可以用作数据库、缓存和消息中间件。作为一个高性能的键值存储系统,Redis提供了丰富的命令和功能,其中包括对集合(Set)的操作。在Redis中,集合是一个无序的、不重复的字符串集合。本文将介绍如何使用Redis命令来剔......
  • Java-集合-八股文
    list、setlist:有序,可重复,允许多个null,支持下标随机访问set:无序,不可重复,单一null,必须遍历访问arraylist、linkedlistarraylist:基于数组实现,占用连续空间,有利于查找、修改,不利于插入、删除[适用场景不同]linkedlist:基于链表实现,不要求占用空间连续,有利于插入、删除,不利于......
  • 周日 集合
    集合是手机和组织其他对象的对象集合中的元素通常根据他们添加到集合的顺序或者元素之间的某种内在关系来组织集合是一种抽象,其隐藏了实现细节 数据结构是用于实现集合的底层编程结构栈元素以LIFO方式处理:最后入栈的元素第一个出栈push将元素添加到栈顶 pop从栈顶删除元......
  • Java 集合和流
    (目录)一、从集合中获取流JavaStreamAPI提供了一种更实用的编程方法来迭代和处理集合等元素。JavaStreamAPI是在Java8中添加到Java中的。流大部分是和Javalambda表达式一起使用,不熟悉lambda表达式的建议了解之后阅读。可以通过调用给定集合的方法从集合中获取流......
  • 特殊类型注入-集合类型
    集合配置配置集合和配置数组差不多,集合采用list标签,标签下再使用ref引用外部bean<beanid="dept"class="com.study.spring6.iocxml.deptAndEmp.Dept"><propertyname="dName"value="IT"/><propertyname="emps">......
  • 特殊类型注入-数组与集合
    数组给Emp添加上属性privateString[]love;表示员工爱好配置<beanid="dept"class="com.study.spring6.iocxml.deptAndEmp.Dept"><propertyname="dName"value="IT"/></bean><beanid="emp"class=......
  • python怎么将集合的数字相加起来
    Python如何将集合的数字相加起来在Python中,如果我们有一个集合(set)包含了一些数字,我们可以使用不同的方法来将这些数字相加起来。下面将介绍一些常用的方法和示例代码。方法一:使用循环遍历集合我们可以使用循环遍历集合的每个元素,然后将它们累加起来。numbers={1,2,3,4,5}......