Description
有 n−1 n − 1 个寿司,第 i i 个寿司的美味度为 。小 G G 和小 W W 每人选择一些寿司来品尝。规定一种方案为不和谐的当且仅当:小 G G 和小 W W 品尝的寿司中分别存在美味度为 x x 和 的寿司,且 x x 和 不互质。求一共有多少种方案是和谐的。
2⩽n⩽500 2 ⩽ n ⩽ 500
Solution
问题的本质为:两个人在 2∼n 2 ∼ n 中选取数字,第一个人和第二个人选择的数字的质因子集合分别为 S1 S 1 和 S2 S 2 ,满足 S1∩S2=∅ S 1 ∩ S 2 = ∅ 的选择方案数。
30 ptsn⩽30 30 pts n ⩽ 30
注意到
30
30
以内的质数只有
10
10
个,考虑使用状压
DP
DP
计算答案。可以将每个数分解质因数并压成二进制数
S
S
(其中 的第
i
i
位表示是否有第 个质因子)。
设
f[i][S1][S2]
f
[
i
]
[
S
1
]
[
S
2
]
表示考虑到第
i
i
个数,第一个人选择的数的质因子集合为 ,第二个人选择的数的质因子为
S2
S
2
,有如下状态转移方程:
f[i][S1|k][S2]+=f[i−1][S1][S2]
f
[
i
]
[
S
1
|
k
]
[
S
2
]
+
=
f
[
i
−
1
]
[
S
1
]
[
S
2
]
(
k∩S2=∅
k
∩
S
2
=
∅
)
f[i][S1][S2|k]+=f[i−1][S1][S2]
f
[
i
]
[
S
1
]
[
S
2
|
k
]
+
=
f
[
i
−
1
]
[
S
1
]
[
S
2
]
(
k∩S1=∅
k
∩
S
1
=
∅
)
其中第一维可以压缩,答案为
∑S1∩S2=∅f[n][S1][S2]
∑
S
1
∩
S
2
=
∅
f
[
n
]
[
S
1
]
[
S
2
]
100 ptsn⩽500 100 pts n ⩽ 500
按照之前的状态压缩显然不可行,考虑如何将状态继续压缩。
注意每个数字
x
x
至多只有一个大于 的质因子,因此我们可以将不大于
x−−√
x
的质因子进行
DP
DP
,单独考虑大于
x−−√
x
的质因子。
小于
x−−√max=500−−−√≈22.4
x
max
=
500
≈
22.4
的质因子只有
8
8
个,于是我们对这些质因子按照一般的状压 进行计算。
考虑大于
x−−√
x
的质因子
p
p
,如果其中一个人选择了含有质因子 的某个数,那么另一个人就不能选择任何含有质因子
p
p
的数字。设 表示第
k
k
个人选择了质因子 ,第一个人和第二个人选择的小于
n−−√
n
的质因子集合分别为
S1
S
1
和
S2
S
2
,转移显然。
在计算完
g
g
数组后,再更新 的答案。考虑如何将两个数组合并:(稍有常识的人就能发现)
f[S1][S2]=g[0][S1][S2]+g[1][S1][S2]−f[S1][S2]−f[S1][S2]
f
[
S
1
]
[
S
2
]
=
g
[
0
]
[
S
1
]
[
S
2
]
+
g
[
1
]
[
S
1
]
[
S
2
]
−
f
[
S
1
]
[
S
2
]
−
f
[
S
1
]
[
S
2
]
最后减去
f[S1][S2]
f
[
S
1
]
[
S
2
]
是因为两个人都不选这个集合的数的情况被统计了
2
2
次。
时间复杂度:
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=505;
const int P=8;
const int prime[P]={2,3,5,7,11,13,17,19};
int n,p,f[1<<P][1<<P],g[2][1<<P][1<<P];
pair<int,int> s[N];
int main() {
scanf("%d%d",&n,&p);
for(int i=2;i<=n;++i) {
int tmp=i;
for(int j=0;j<8;++j) while(tmp%prime[j]==0) s[i].second|=(1<<j),tmp/=prime[j];
s[i].first=tmp;
}
std::sort(s+1,s+n+1);
f[0][0]=1;
for(int i=2;i<=n;++i) {
if(i==2||s[i].first==1||s[i].first!=s[i-1].first) {
memcpy(g[0],f,sizeof(f));
memcpy(g[1],f,sizeof(f));
}
for(int s1=(1<<P)-1;s1>=0;--s1)
for(int s2=(1<<P)-1;s2>=0;--s2) {
if(s1&s2) continue;
if((s2&s[i].second)==0) (g[0][s1|s[i].second][s2]+=g[0][s1][s2])%=p;
if((s1&s[i].second)==0) (g[1][s1][s2|s[i].second]+=g[1][s1][s2])%=p;
}
if(i==n||s[i].first==1||s[i].first!=s[i+1].first) {
for(int s1=(1<<P)-1;s1>=0;--s1)
for(int s2=(1<<P)-1;s2>=0;--s2)
if((s1&s2)==0) f[s1][s2]=((g[0][s1][s2]+g[1][s1][s2]-f[s1][s2])%p+p)%p;
}
}
int ans=0;
for(int i=(1<<P)-1;i>=0;--i)
for(int j=(1<<P)-1;j>=0;--j)
if((i&j)==0) (ans+=f[i][j])%=p;
printf("%d\n",ans);
return 0;
}