Description
为了庆祝 NOI 的成功开幕,主办方为大家准备了一场寿司晚宴。小 G 和小 W 作为参加 NOI 的选手,也被邀请参加了寿司晚宴。
在晚宴上,主办方为大家提供了 n−1 种不同的寿司,编号 1,2,3,…,n−1,其中第 i 种寿司的美味度为 i+1 (即寿司的美味度为从 2 到 n)。
现在小 G 和小 W 希望每人选一些寿司种类来品尝,他们规定一种品尝方案为不和谐的当且仅当:小 G 品尝的寿司种类中存在一种美味度为 x 的寿司,小 W 品尝的寿司中存在一种美味度为 y 的寿司,而 x 与 y 不互质。
现在小 G 和小 W 希望统计一共有多少种和谐的品尝寿司的方案(对给定的正整数 p 取模)。注意一个人可以不吃任何寿司。
Input
输入文件的第 1 行包含 2 个正整数 n,p,中间用单个空格隔开,表示共有 n 种寿司,最终和谐的方案数要对 p 取模。
Output
输出一行包含 1 个整数,表示所求的方案模 p 的结果。
Sample Input
3 10000
Sample Output
9
HINT
2≤n≤500
0<p≤1000000000
首先说一句:寿司真好吃(逃
咳,我们回归正题。。。首先看着题就没法状压,(nleqslant 500)完全下不去手。。。然后考虑互质的话,我们有一个套路做法,就是分解质因数。不过500内的质因数也有上百个,还是压不了。。。不过,我们发现一件事情,有很多质因数在每个数里至多只会出现一次!!!
好,我们缕一下思路,首先,有一些质数会在一个数中出现很多次,他们都(leqslantsqrt{500}),这些质数共计8个。我们对这8个数进行状压,然后剩下的那些质因数,由于它们至多在一个数中出现一次,那么就说明,它们当中有一个被一个人选了,另一个人就不能选这些数了。
好,我们再次缕一下思路,我们把这些拥有相同大质因数的数放到一堆来,类似于分块的思想。然后记(F[S1][S2])代表小G选的寿司中,前8个质数的状态为S1;小W选的寿司中,前8个质数的状态为S2。然后这样不好在块内转移,我们就再开个(G[2][S1][S2]),每到一个块中,(G[0]=G[1]=F),然后(G[0])代表当前块所代表的大质数被小G选了,(G[1])表示大质数被小W选了。
然后这个块处理完之后,(F=G[0]+G[1]-F),-F是因为不选的情况被考虑了两次,所以要减掉
具体的,看代码吧。。。
/*program from Wolfycz*/
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
inline int read(){
int x=0,f=1;char ch=getchar();
for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
return x*f;
}
inline void print(int x){
if (x>=10) print(x/10);
putchar(x%10+'0');
}
const int N=5e2;
const int prime[8]={2,3,5,7,11,13,17,19};
struct S1{
int sta,p;
bool operator <(const S1 &x)const{return p<x.p;}
}A[N+10];
int f[(1<<8)+10][(1<<8)+10];
int g[2][(1<<8)+10][(1<<8)+10];
int n,p,All;
int main(){
n=read(),p=read(),All=(1<<8)-1;
for (int i=2;i<=n;i++){
int x=i;
for (int k=0;k<8;k++){
if (x%prime[k]==0) A[i].sta|=1<<k;//记录前8个质数的状态
while (x%prime[k]==0) x/=prime[k];
}
A[i].p=x;//记录大质数,为1代表没有大质数
}
sort(A+2,A+1+n);
f[0][0]=1;
for (int i=2;i<=n;i++){
if (i==2||A[i].p!=A[i-1].p||A[i].p==1){//如果是块的开始,或者没有大质数,就令g[0]=g[1]=f
memcpy(g[0],f,sizeof(f));
memcpy(g[1],f,sizeof(f));
}
for (int j=All;~j;j--){
for (int k=All;~k;k--){
if (j&k) continue;
if (!(A[i].sta&k)) g[0][j|A[i].sta][k]=(g[0][j|A[i].sta][k]+g[0][j][k])%p;//小G选了不会和小W有冲突
if (!(A[i].sta&j)) g[1][j][k|A[i].sta]=(g[1][j][k|A[i].sta]+g[1][j][k])%p;//和上面同理
}
}
if (i==n||A[i].p!=A[i+1].p||A[i].p==1)//如果是块的结束,或者没有大质数,则令F=G[0]+G[1]-F
for (int j=All;~j;j--)
for (int k=All;~k;k--)
if (!(j&k))
f[j][k]=((g[0][j][k]+g[1][j][k]-f[j][k])%p+p)%p;
}
int Ans=0;
for (int j=All;~j;j--)
for (int k=All;~k;k--)
if (!(j&k))
Ans=(Ans+f[j][k])%p;
printf("%d
",Ans);
return 0;
}