题解 大包子的完美组合

题目描述

题目链接
一开始怎么都没想到状压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;
}
原文地址:https://www.cnblogs.com/ezlmr/p/13325120.html