题解 大包子的完美组合

题目描述

题目链接
一开始怎么都没想到状压DP, 后来看了题解才想到的

题解

前置知识

为了解决本题,你应该要会

  • 状压DP
  • 素数筛法(打表其实也可以)

思路

状态

看到题,容易想到这是一道DP
观察题目,发现应该是以当前有没有出现某个数作为状态
\(dp[i]\)就表示前\(i\)个人可以选出的完美组合的方案数

又因为题目要求编号两两互质,所以说肯定要将当前已选的素数记录下来
所以说要使用状压DP
但我们发现题目中\(n \leq 3000\)
\(3000\)以内的质数有\(430\)个,也就是说状态数多达\(2^{430}-1\)个,肯定存不下

我们该如何减少状态数呢?我们看回题目,发现一些组合中除了包含一些素数编号,还包含了另一些素数乘积的编号
那么对于一些素数来说,由于它在数内最多出现一次(即大于\(\sqrt{n}\)),
所以说我们即使直接枚举这些素数的倍数,也无需考虑倍数中出现该素数的情况
所以说我们只需要记录小于\(\sqrt{n}\)的素数即可,这样状态数最多只有\(2^{16}-1\)

至此,我们的状态就定下来了,也就是dp[i][st],
其中i表示前\(i\)个人可以选出的完美组合的方案数,st表示当前组合中包含的素数

预处理

首先,我们需要先求出\(\sqrt{n}\)中的素数
然后我们遍历\(1-n\),对于每一个数\(x\)

  • 如果\(x\)存在大于\(\sqrt{n}\)的质因子\(p\)\(\frac{x}{p}\)就是\(p\)可取的倍数之一,那我们就将\(\frac{x}{p}\)质因数分解,并将分解得到的状态存入\(p\)记录倍数的数组
  • 如果\(x\)不存在大于\(\sqrt{n}\)的质因子,那么我们就将\(x\)质因数分解得到的状态存入\(x\)记录因数的数组中

状态转移方程

我们遍历\(2-n\),对于前\(x\)个人可以选出的的方案数

  • 如果\(x\)是大于\(\sqrt{n}\)的质数
    那么对于所有可行的倍数\(k\),我们都在将与\(k\)互质的方案上加入\(xk\),最后再加入单独的\(xk\)与原先的状态
  • 如果\(x\)是大于\(\sqrt{n}\)的质数的倍数
    那么因为\(x\)所有的倍数都已经遍历过了,所以跳过当前\(x\)
  • 如果\(x\)不存在大于\(\sqrt{n}\)的质因子
    对于\(x\)质因数分解的状态\(k\),对于所有与\(k\)互质的方案都加入\(x\),最后再加入单独的\(x\)与原先的状态

通过观察我们可以发现,三种情况代码其实是一样的
然后我们只需要求出对于前\(n\)个人可以选出的的方案数的和就可以了
当然,我们还要记得对于所有方案都加入\(1\),最后再加入单独的\(1\)与原先的状态就可以了

代码

#include<iostream>
#include<vector>
#define mod 1000000007
using namespace std;
int n;
int cnt,phi[20];
bool vis[3005];
vector<int>g[3005];
long long dp[2][70000];
int main() {
	cin>>n;
	for(int i=2; i<=n; i++) {
		if(vis[i]==false) {
			phi[cnt++]=i;
			for(int j=i; j<=n; j+=i)vis[j]=true;
		}
	}//筛素数 
	for(int i=1; i<=n; i++) {
		int x=i,num=0;
		for(int j=0; j<cnt; j++) {
			while(x%phi[j]==0) {
				x=x/phi[j];
				num=num|(1<<j);
			}
		}
		if(x==1)g[i].push_back(num);//当x存在大于n的开方的质因子p
		else g[x].push_back(num);//当x不存在大于n的开方的质因子p
	}
	dp[0][0]=1;//初始化 
	for(int i=2;i<=n;i++){
		for(int j=0;j<g[i].size();j++){
			for(int k=0;k<(1<<cnt);k++){
				if(k&g[i][j])continue;//k必须与g[i][j]出现的素数不同,即互质 
				dp[1][k|g[i][j]]=(dp[1][k|g[i][j]]+dp[0][k])%mod;//对于原本的方案加入倍数或本身 
					//如今两个素数都包含 
			}
			dp[1][g[i][j]]++;//加入它本身 
		}
		for(int j=0;j<(1<<cnt);j++){
			dp[0][j]=(dp[0][j]+dp[1][j])%mod;//加入原先的方案 
			dp[1][j]=0;
		}
	}
	long long ans=0;
	for(int i=0;i<(1<<cnt);i++)ans=(ans+dp[0][i])%mod;//求出总方案数 
	cout<<(ans*2+1)%mod;//求出包含1的方案 
	return 0;
}
posted @ 2020-07-16 21:41  ezlmr  阅读(166)  评论(0编辑  收藏  举报