题目链接(洛谷)
题目大意
有\([2,n]\) 一共\(n-1\) 个数,甲乙两个人分别取子集\(S,T\) ,要求不存在\(x\in S,y\in T\) ,使得\(\gcd(x,y) \neq 1\) 。求取子集的方案个数。
对于\(30\%\) 的数据,\(2\le n\le 30\) ;对于\(100\%\) 的数据,\(2\le n\le 500\)
分析
暴力
两数互质,则两数没有相同质因数。对于\(n\le 30\) 的数据,可能的质因数只有前\(10\) 个,我们可以状压表示每个质因数的使用状态。
定义\(f(i,s_1,s_2)\) 表示前\(i\) 个数中,甲、乙选的集合的质因数状态分别为\(s_1,s_2\) 。用\(k\) 表示\(i\) 的质因数状态,则有转移:
\[
\begin{aligned}
f(i,s_1\operatorname{|}k,s_2)\operatorname{+=}f(i-1,s_1,s_2)~ (k \operatorname{\&} s_2 = 0)\\
f(i,s_1,s_2\operatorname{|}k)\operatorname{+=}f(i-1,s_1,s_2)~ (k \operatorname{\&} s_1 = 0)
\end{aligned}
\]
可以用滚动数组把第一维优化掉。复杂度\(O(n\times 2^{20})\) 。
优化
当\(n\le 500\) 时质数变多了,无法将它们全部状压。注意到一个数\(n\) 最多有一个比\(\sqrt{n}\) 大的质因子。对\(n\) 质因数分解的时候,可以将这个大质数(\(\ge 23\) )单独记下来,剩下的小质数状压。
先以大质数为关键字排序,把大质数相同的数放在一起。有同一大质数的数不可能同时出现在甲和乙的集合中。 所以我们将\(f(s_1,s_2)\) 数组暂时分成两个,\(g_1(s_1,s_2),g_2(s_1,s_2)\) 分别表示当前大质数只放进甲/乙的集合,甲乙小质数状态分别为\(s_1,s_2\) 时的方案数。
\(g_1,g_2\) 直接按照\(30\) 分做法转移,当前大质数全部转移完后将答案合并到原来的\(f\) 数组里,下一个大质数转移开始之前,将\(g_1,g_2\) 附成当前\(f\) 。
$$
\[\begin{aligned}
&g_1(s_1\operatorname{|}k,s_2)\operatorname{+=}g_1(s_1,s_2)~(s_2 \operatorname{\&} k=0)\\
&g_2(s_1,s_2\operatorname{|}k)\operatorname{+=}g_2(s_1,s_2)~(s_1 \operatorname{\&}k=0)\\
&f(s_1,s_2)=g_1(s_1,s_2)+g_2(s_1,s_2)-f(s_1,s_2)
\end{aligned}\]
$$
答案即\(\sum f(s_1,s_2)~(s_1\operatorname{\&}s_2=0)\)
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include <bits/stdc++.h> #define ll long long using namespace std;int pri[] = {0 , 2 , 3 , 5 , 7 , 11 , 13 , 17 , 19 };ll n, p, ans, f[300 ][300 ], g1[300 ][300 ], g2[300 ][300 ]; inline void mo (ll &x) { x = (x + p) % p; } struct node { ll big, sta; bool operator <(const node &x) const { return big < x.big; } } a[505 ]; void getSta () { for (ll i = 1 ; i < n; i++) { ll sta = 0 , tmp = i + 1 ; for (int j = 1 ; j <= 8 ; j++) { if (tmp % pri[j]) continue ; sta |= 1 << (j - 1 ); while (tmp % pri[j] == 0 ) tmp /= pri[j]; } a[i].big = tmp; a[i].sta = sta; } } void sol () { sort (a + 1 , a + n); f[0 ][0 ] = 1 ; for (ll i = 1 ; i < n; i++) { if (i == 1 || a[i].big != a[i - 1 ].big || a[i].big == 1 ) { memcpy (g1, f, sizeof (g1)); memcpy (g2, f, sizeof (g2)); } for (ll j = 255 ; j >= 0 ; j--) { for (ll k = 255 ; k >= 0 ; k--) { if (j & k) continue ; if ((a[i].sta & k) == 0 ) g1[j | a[i].sta][k] += g1[j][k], mo (g1[j | a[i].sta][k]); if ((a[i].sta & j) == 0 ) g2[j][k | a[i].sta] += g2[j][k], mo (g2[j][k | a[i].sta]); } } if (i == n - 1 || a[i].big != a[i + 1 ].big || a[i].big == 1 ) { for (ll j = 255 ; j >= 0 ; j--) { for (ll k = 255 ; k >= 0 ; k--) f[j][k] = g1[j][k] + g2[j][k] - f[j][k], mo (f[j][k]); } } } } void getAns () { for (ll i = 255 ; i >= 0 ; i--) { for (ll j = 255 ; j >= 0 ; j--) { if (i & j) continue ; ans += f[i][j], mo (ans); } } } int main () { ios::sync_with_stdio (false ); cin >> n >> p; getSta (); sol (); getAns (); cout << ans << endl; return 0 ; }