链接
能学好多东西
题意描述:
两个人各从正整数 2 ~ n(<=500) 中取一些数,可以不取;若取出的两个集合中,任意属于不同集合的的两个元素都互质,则方案合法;求合法方案数
首先注意到一个方案合法的等价条件是:两个人的质数集合没有交集
可以想到 \(f[s1][s2]\) 表示两个人的质数集合为 s1 和 s2 \((s1\and s2=0)\) 时的方案数,当然还有隐藏的一维:前 i 个数
设第 i 个数的质数集合为 s,则有递推式:
但是 n=500 时显然状压不下那么多的质数
我们需要注意到一个性质:对于一个数 \(x\),最多只有一个大于 \(\sqrt x\) 的质因子
对应此题,即所有数最多只有一个大于 22 的质因子,而小于 22 的质数只有 8 个
我们可以考虑只状压前 8 个质数,对于更大的质数的不重复取,用排序来处理:那个大质数,要么属于集合 s1,要么 s2,要么都不属于
对每个数字,预处理得到其质因子的前 8 个质数的集合 s,和唯一一个大质数 p(无则设为 1)
所有数字按 p 排序,然后递推:
设 \(f[i][s1][s2]\) 表示前 i 个数,(前 8 个)质数集合为 s1, s2 的方案数,设 \(f1[i][s1][s2]\) 表示当前的大质数只可能由第 1 个人取的方案数,\(f2[i][s1][s2]\) 同理
则对于当前出现的一个新的大质数(对应的一段区间),首先将 f 复制给 f1, f2,然后 f1 和 f2 按各自的规则递推,最后再相加赋值给 f;两者都不包括当前质数的情况算了两次,所以要扣除一倍 f:\(f[s1][s2] = f1[s1][s2] + f2[s1][s2] - f[s1][s2]\)
实现的时候我们可以不用滚动数组,注意到被贡献的 s1 始终小等于被修改的 s1|s(且每个数只被贡献出去一次),所以可以从大往小枚举 s1, s2
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int pri[8] = {2, 3, 5, 7, 11, 13, 17, 19};
const int INF = 260;
int N; ll mod;
ll f[INF][INF], f1[INF][INF], f2[INF][INF];
struct node {
int s, p;
void cal(int x) {
s = 0;
for (int i=0; i< 8; i++) if (x%pri[i]==0) {
s |= (1<<i);
while (x%pri[i]==0) x /= pri[i];
}
p = x;
}
} P[505];
int cmp(node x, node y) { return x.p < y.p; }
void add(ll &x, ll y) { x += y; if (x> mod) x -= mod; }
int main()
{
scanf("%d%lld", &N, &mod);
for (int i=2; i<=N; i++) P[i-1].cal(i);
sort(P+1, P+N, cmp);
f[0][0] = 1;
for (int i=1; i< N; i++) {
if (P[i].p==1 || P[i].p!=P[i-1].p)
memcpy(f1, f, sizeof(f)), memcpy(f2, f, sizeof(f));
int s = P[i].s;
for (int s1=255; s1>=0; s1--) {
for (int s2=255; s2>=0; s2--) {
if (s1&s2) continue;
if (!(s&s2)) add(f1[s1|s][s2], f1[s1][s2]);
if (!(s&s1)) add(f2[s1][s2|s], f2[s1][s2]);
// [s1][s2] is contributed only once
}
}
if (i==N-1 || P[i].p==1 || P[i].p!=P[i+1].p) {
for (int s1=0; s1<=255; s1++) {
for (int s2=0; s2<=255; s2++) {
if (s1&s2) continue;
f[s1][s2] = (f1[s1][s2]+f2[s1][s2]+mod-f[s1][s2])%mod;
}
}
}
}
ll ans = 0;
/* for (int s1=0; s1<=3; s1++)
for (int s2=0; s2<=3; s2++)
printf("f[%d][%d]=%lld\n", s1, s2, f[s1][s2]);*/
for (int s1=0; s1<=255; s1++)
for (int s2=0; s2<=255; s2++) add(ans, f[s1][s2]);
printf("%lld\n", ans);
}