【Luogu P2150】[NOI2015] 寿司晚宴
链接:
题目大意:
有 \(n-1\) 个数,第 \(i\) 数是 \(i+1\)。在这其中选两组数,求这两组数不互质的方案数。
\(n\leq500\)。
正文:
30pts:
分解质因数,在 \(n\leq30\) 以内的素数只有 \(10\) 个,设 \(f_{i,a,b}\) 表示当前操作到第 \(i\) 个数,第一组的素数集合是 \(a\),第二组的素数集合是 \(b\) 的方案数。
于是可以这么转移:
\[f_{i,a,b}\Rightarrow\left\{\begin{matrix}
f_{i+1,a\cup k,b} & (b\cap k=\varnothing)\\
f_{i+1,a,b\cup k} & (a\cap k=\varnothing)
\end{matrix}\right.\]
进行滚动数组,还可以优化空间。
时间复杂度 \(\mathcal{O}(2^{20}n)\)。
100pts:
一个小于等于 \(n\) 的数只有若干个小于等于 \(\sqrt{n}\) 的小质因数和最多一个大于 \(\sqrt{n}\) 的大质因数。因为题目中有给定要互质,那么含有大质因数的数要么全分到 \(a\) 要么全分到 \(b\),那么再设 \(g_{a,b},h_{a,b}\) 分别表示当前有大质因数的数全分给 \(a\) 或 \(b\) 的方案数。然后对每个数按大质因数分组(若没有,自己为独立一组),按组统计,合并。合并时,\(f_{a,b}=g_{a,b}+h_{a,b}-f_{a,b}\),减去的是两组重复计算的两组都没有分到任何一个有大质因数的数的方案数。
代码:
const int N = 510;
inline ll READ()
{
ll x = 0, f = 1;
char c = getchar();
while (c != '-' && (c < '0' || c > '9')) c = getchar();
if (c == '-') f = -f, c = getchar();
while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
return x * f;
}
int n;
ll p;
int pri[10] = {2, 3, 5, 7, 11, 13, 17, 19};
struct Val
{
int val, part1, part2;
Val(){part2 = -1;}
void Set(int x)
{
part2 = 0;
val = x;
for (int i = 0; i < 8; i++)
for (; !(x % pri[i]); x /= pri[i]) part1 |= 1 << i;
if (x != 1) part2 = x;
}
bool operator < (Val &a) const
{
return part2 < a.part2;
}
}a[N];
ll f[N][N], g[N][N], h[N][N], ans;
int main()
{
n = READ(), p = READ();
for (int i = 1; i < n; i++) a[i].Set(i + 1);
sort (a + 1, a + n);
f[0][0] = 1;
for (int i = 1; i < n; i++)
{
if (i == 1 || a[i].part2 != a[i - 1].part2 || !a[i].part2)
{
memcpy(g, f, sizeof g);
memcpy(h, f, sizeof h);
}
for (int j = 256; j >= 0; j--)
for (int k = 256; k >= 0; k--)
if(!(j & k))
{
if (!(a[i].part1 & k)) (g[j | a[i].part1][k] += g[j][k]) %= p;
if (!(a[i].part1 & j)) (h[j][k | a[i].part1] += h[j][k]) %= p;
}
if (i == n - 1 || a[i].part2 != a[i + 1].part2 || !a[i].part2)
{
ans = 0;
for (int j = 0; j <= 256; j++)
for (int k = 0; k <= 256; k++)
if(!(j & k)) f[j][k] = (g[j][k] + h[j][k] + p - f[j][k]) % p, ans = (ans + max(f[j][k], 0ll)) % p;
if (i == n - 1) printf ("%lld\n", ans);
}
}
return 0;
}