题目描述
为了庆祝NOI的成功开幕,主办方为大家准备了一场寿司晚宴。小G和小W作为参加NOI的选手,也被邀请参加了寿司晚宴。
在晚宴上,主办方为大家提供了n−1种不同的寿司,编号1,2,3,⋯,n-1,其中第种寿司的美味度为i+1(即寿司的美味度为从2到n)。
现在小G和小W希望每人选一些寿司种类来品尝,他们规定一种品尝方案为不和谐的当且仅当:小G品尝的寿司种类中存在一种美味度为x的寿司,小W品尝的寿司中存在一种美味度为y的寿司,而x与y不互质。
现在小G和小W希望统计一共有多少种和谐的品尝寿司的方案(对给定的正整数p取模)。注意一个人可以不吃任何寿司。
输入输出格式
输入格式:
从文件dinner.in中读入数据。
输入文件的第1行包含2个正整数n,p中间用单个空格隔开,表示共有n种寿司,最终和谐的方案数要对p取模。
输出格式:
输出到文件dinner.out中。
输出一行包含1个整数,表示所求的方案模p的结果。
输入输出样例
输入样例#1:
3 10000
输出样例#1:
9
输入样例#2:
4 10000
输出样例#2:
21
输入样例#3:
100 100000000
输出样例#3:
3107203
n<=500 p<=1e9
solution:
选数等价于选了这个数所有的质因子,所以按质因子来考虑问题,
即两个集合不能有相同的质因子
先考虑n 很小比如30 时,则2 到30 间的质数只有10 个,可以考虑状态压缩
f[i][A][B] 表示前i 个数,A 集合所选质数状态为A,B 集合所选质数状态为B 的方案数
预处理出每个数的质因子分解并转成二进制状态pi,转移的时候有三种选择,对应着f[i + 1][A][B]; f[i + 1][Ajpi][B]; f[i + 1][A][Bjpi]
最后枚举A; B 并将A&B 为0 的状态的值统计进答案
现在n 很大,质数变多了无法状压,还需要进一步考虑
注意到每个数至多包含一个>sqrt(n) 的质因子,而<=sqrt(n) 的质因子一共只有9 个,
所以考虑状压小质因子,暴力算大质因子
f[A][B] 表示前i 个大质数,A 集合包含的小质数状态为A,B 集合包含的小质数状态为B 的方案数
转移时,枚举包含i 的所有数,此时转移与之前的暴力类似有三种选择
注意此时所有数都含某个大质因子,所以要么都放入A 要么都放入B 要么都不放
可以利用一个辅助数组g[0/1][A][B] 来帮助计算这一个过程
时间复杂度O(n*2^18)
状压dp好题啊
关于互质一般就会想到质因子,因为质因子一般会很少并且满足一些性质就像上面说的
而质因子很少的时候可以考虑状压,dp的时候有一些额外需要的信息
可以开辅助数组来记录
还有注意状压的数组空间和时间复杂度
具体细节看注释:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define maxn 505
#define LL long long
#define re register
using namespace std;
int n;
int pri[15]={2,3,5,7,11,13,17,19,23};
LL p,f[1<<9][1<<9],g[2][1<<9][1<<9],ans;//我的妈这里开成1<<10居然也会t
const int mx=255;//注意这里开成511就会t掉一个点
inline LL rd(){
LL x=0,f=1;char c=' ';
while(c<'0' || c>'9') {if(c=='-')f=-1;c=getchar();}
while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
return x*f;
}
struct Sushi{
int S,prime;
}a[maxn];
inline bool cmp(Sushi x,Sushi y){return x.prime<y.prime;}
int main(){
n=rd(); p=rd();
for(re int i=1;i<=n;i++){
int x=i;
for(re int j=0;j<8;j++){
if(x%pri[j]>0) continue;
if(x<pri[j]) break;
a[i].S|=(1<<j);
while(x%pri[j]==0) x/=pri[j];
}
a[i].prime=x;
}
sort(a+2,a+n+1,cmp);//要排序
f[0][0]=1;
for(re int i=2;i<=n;i++){//这里是判断这个数是包含这个大质数的数的第一个
if(i==2 || a[i].prime!=a[i-1].prime || a[i].prime==1){
memcpy(g[0],f,sizeof f); memcpy(g[1],f,sizeof f);
}
for(re int j=mx;j>=0;j--)
for(re int k=mx;k>=0;k--){//这里必须倒着计算,不然可能算重了
if(j&k) continue;
if((a[i].S&k)==0)//放到第一个里行不行
(g[0][a[i].S|j][k]+=g[0][j][k])%=p;
if((a[i].S&j)==0)
(g[1][j][a[i].S|k]+=g[1][j][k])%=p;
}
if(i==n || a[i].prime==1 || a[i].prime!=a[i+1].prime)//这里判断这个数是包含这个大质数的最后一个
for(re int j=0;j<=mx;j++)
for(re int k=0;k<=mx;k++){
if(j&k)continue;
f[j][k]=g[0][j][k]+g[1][j][k]-f[j][k];
(f[j][k]+=p)%=p;//注意这里可能是负数
}//这里g0和g1都计算过两遍f[j][k],所以要减掉一个
}
for(re int i=0;i<=mx;i++)
for(re int j=0;j<=mx;j++)
if(!(i&j)) (ans+=f[i][j])%=p;
printf("%lld\n",ans);
return 0;
}