*题解:P3226 [HNOI2012] 集合选数

题目链接

解析

注意到题面里的课程名称为《集合论与图论》,考虑对于每个 \(x\),向 \(2x\)\(3x\) 连边,当 \(n = 18\) 时,画出来的图应该是这样的:

这样看的话有 \(O(\log_2 n)\) 行与 \(O(\log_3 n)\) 列,问题变为炮兵阵地弱化版,可以使用状压 DP 来解决。

时间复杂度 \(O(n\log n + k ^ 2 \log n)\)。其中 \(k\) 表示从序列中选取的元素互不相邻的方案数。

代码

#include<bits/stdc++.h>
#define ls(x) ((x) << 1)
#define rs(x) (((x) << 1) | 1)
#define mid ((l + r) >> 1)
#define getchar() getchar_unlocked()
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef pair<int,int> pii;
const int N = 1e5 + 5,M = 18,mod = (int)1e9 + 1; 
vector<vector<int> > v[N];
int f[M][(1 << M) + 5];
vector<int> num[M];
bool vis[N];
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);	
//	freopen("in.txt","r",stdin);
//	freopen("out1.txt","w",stdout);
	int n;
	cin>>n;
	for(int i=1;i<M;i++){
		for(int j=0;j<(1 << i);j++){
			bool flag = true;
			for(int k=0;k<i;k++){
				if((j & (1 << k)) && (j & (1 << (k + 1)))){
					flag = false;
					break;
				}
			}
			if(!flag) continue;
			num[i].push_back(j);
		}
	} 
	int cnt = 0;
	for(int i=1;i<=n;i++)if(!vis[i]){
		cnt++;
		for(int j=1;i * j <= n;j *= 2){
			v[cnt].push_back({});
			for(int k=1;i * j * k <= n;k *= 3){
				vis[i * j * k] = true;
				v[cnt].back().push_back(i * j * k);
			}
		}
		
	}
	int res = 1;
	for(int i=1;i<=cnt;i++){	
		for(int j : num[v[i][0].size()]){
			f[0][j] = 1;
		}
		for(int l=1;l<(int)v[i].size();l++){
			int now = v[i][l].size(),pre = v[i][l - 1].size();
			for(int j : num[now]){
				f[l][j] = 0;
				for(int k : num[pre]){
					bool flag = !(j & k);
					if(flag){
						f[l][j] = (f[l][j] + f[l - 1][k]) % mod;
					}
				}
			}
		}
		int sum = 0;
		for(int j : num[v[i].back().size()]){
			sum = (sum + f[(int)v[i].size() - 1][j]) % mod;
		}
		res = 1ll * res * sum % mod;
	}
	cout<<res;
	return 0;
}
posted @ 2026-09-04 20:12  yutar  阅读(7)  评论(0)    收藏  举报