P3226 [HNOI2012]集合选数 题解

纪念一下30紫and500AC

首先先膜拜一下神仙出题人,这题太神仙了。

题意:

要构造一个集合,使得 $ x \in A$ ,满足 $ 2x \notin A$ 且 $ 3x \notin A$ , 求 \(\{1,2,\ldots ,n\}\) 有多少个满足上述约束条件的子集。

思路

看到这题,第一个需要解决的问题时:这是啥题?数学?OEIS?

都不是。显然如果用数学比较难搞。这题是个神仙构造题

参考小学奥数,考虑构造出这样子的矩阵:

1 2 4 8 16 ...
3 6 12 24 ...
9 18 36 72...
....

发现这要选了其中一个数,那么就不能取它上面和下面的数了。

那究竟有多少种方案呢?显然可以用状压DP解决。

但有一个漏洞,在上述矩阵中,5并没有出现过。所以我们需要对于5再构造一个矩阵,以此类推,最后用乘法原理求出答案。

神仙构造题!!赞美出题人!!

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
static char buf[1000000],*p1=buf,*p2=buf;
#define getchar() p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++
inline int read(){int x=0,f=1;char c=getchar();while(c<'0' || c>'9'){if(c=='-') f=-1;c=getchar();}while(c>='0' && c<='9') {x=x*10+c-48;c=getchar();}return x*f;}
inline void write(int x){static char buf[20];static int len=-1;if(x<0)putchar('-'),x=-x;do buf[++len]=x%10,x/=10;while(x);while(len>=0)putchar(buf[len--]+'0');}

const int mod = 1e9+1;
const int MAX = 1e5+10;
bool vis[MAX];	
int a[30][30];
int n;
int f[30];
int dp[20][1<<18];	

void build(int x){
	a[1][1] = x; 		
	for(int i = 2; i <= 17; i++){
		a[i][1] = a[i-1][1] * 3;
		// a[i][1] = min(a[i][1], (long long)(0x3f3f3f3f));
		// a[i][1] %= mod;
		if(a[i][1] > n)	a[i][1] = 0x3f3f3f3f;
	}
	for(int i = 1; i <= 17; i++){
		for(int j = 2; j <= 17; j++){
			a[i][j] = a[i][j-1] * 2;
			// a[i][j] = min(a[i][j], (long long)(0x3f3f3f3f));	
			// a[i][j] %= mod;
			if(a[i][j] > n)	a[i][j] = 0x3f3f3f3f;	
		}
	}
	for(int i = 1; i <= 17; i++)	for(int j = 1; j <= 17; j++)	vis[min(n+1, a[i][j])] = 1;
}

int solve(){
	for(int i = 1; i <= 17; i++){
		f[i] = 0;
		for(int j = 1; j <= 17; j++)	if(a[i][j] <= n)	f[i] = (f[i] << 1) + (bool)(a[i][j]);
	}
	for(int i = 0; i <= 17; i++){
		for(int j = 0; j <= f[i]; j++){
			dp[i][j] = 0;
		}
	}
	dp[0][0] = 1;
	for(int i = 0; i <= 17; i++){
		for(int j = 0; j <= f[i]; j++){
			if(dp[i][j]){
				for(int k = 0; k <= f[i+1]; k++)	if(!(k & (k<<1)) and !(j&k))	dp[i+1][k] += dp[i][j], dp[i+1][k] %= mod;
			}
		}
	}
	return dp[17][0];
}

signed main(){
	n = read();
	int ans = 1;
	for(int i = 1; i <= n; i++){
		if(!vis[i])	build(i), ans *= solve(), ans %= mod;
	}	
	write(ans);
	return 0; 
}
posted @ 2022-10-05 10:44  WRuperD  阅读(28)  评论(0编辑  收藏  举报

本文作者:DIVMonster

本文链接:https://www.cnblogs.com/guangzan/p/12886111.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

这是一条自定义内容

这是一条自定义内容