「题解」Luogu P4152 [WC2014]时空穿梭
Description
多测。
给定一个 \(n\) 维空间,需要在这 \(n\) 维空间内选取 \(c\) 个共线的点,要求:
- 这 \(c\) 个点每维坐标均单调递增,即第 \(i + 1\) 个点的第 \(j\) 维坐标必须严格大于第 \(i\) 个点的第 \(j\) 维坐标。
- 第 \(i\) 维坐标是整数且在 \([1, m_i]\) 中。
Solution
先考虑有多少条直线,一条直线可由 起点 和 终点与起点的差向量 确定。
确定差向量为 \((x_1, x_2, \cdots, x_n)\) 后起点的第 \(i\) 维坐标可以在 \([1, m_i - x_i]\) 中取,共 \(\prod_{i = 1}^n (m_i - x_i)\) 种,此时终点也随之确定。
令 \(d = \gcd(x_1, x_2, \cdots, x_n)\),则这一条差向量上的整点,不算起点与终点共 \(d - 1\) 个(分别是 \(\left(\dfrac{x_1}{d}, \dfrac{x_2}{d}, \cdots, \dfrac{x_n}{d}\right)\) 的 \(1\sim d - 1\) 倍)。
那么除起点与终点,在其中选 \(c - 2\) 个共有 \(\dbinom{d - 1}{c - 2}\) 种方法。
所以答案就是
于是就可以愉快地进行反演了,设 \(m = \min\{m_i\}\)。
此时可以做到 \(\Omicron(Tnm)\)。
首先 \(\left\lfloor\dfrac{m_i}{T}\right\rfloor\) 可以整除分块,然后退回到上一步
其中
是关于 \(T\) 的 \(n\) 次多项式,设系数为 \(f_i\)。
再设
那么在整除分块中
于是我们需要预处理 \(g(n) n^i\) 的前缀和,设其为 \(S(n, c, i)\),这一步是 \(\Omicron(cnm)\) 的。
由于有 \(n\) 个 \(m_i\),所以整除分块中共有 \(\Omicron(n\sqrt m)\) 个块,再加上每个块需要 \(\Omicron(n)\) 求和,所以这一段是 \(\Omicron(Tn^2\sqrt m)\) 的。
最后就是求出系数 \(f_i\) 了,直接模拟多项式乘法 \(\Omicron(n^2)\) 计算即可,你也可以写分治 NTT。
总时间复杂度为 \(\Omicron(cm\ln m + cnm + Tn^3\sqrt m)\)。
Code
// 18 = 9 + 9 = 18.
#include <iostream>
#include <cstdio>
#include <cstring>
#define Debug(x) cout << #x << "=" << x << endl
typedef long long ll;
using namespace std;
namespace IO
{
int len = 0;
char buf[(1 << 20) + 1], *S, *T;
#if ONLINE_JUDGE
#define Getchar() (S == T ? T = (S = buf) + fread(buf, 1, (1 << 20) + 1, stdin), (S == T ? EOF : *S++) : *S++)
#else
#define Getchar() getchar()
#endif
#define re register
int read()
{
re char c = Getchar();
re int x = 0;
while (c < '0' || c > '9')
c = Getchar();
while (c >= '0' && c <= '9')
x = (x << 3) + (x << 1) + (c ^ 48), c = Getchar();
return x;
}
}
using IO::read;
const int MOD = 10007;
const int INV = 5004;
int add(int a, int b) {return (a + b) % MOD;}
int sub(int a, int b) {return (a - b + MOD) % MOD;}
int mul(int a, int b) {return (ll)a * b % MOD;}
const int MAXN = 15;
const int N = 11;
const int MAXC = 25;
const int C = 20;
const int MAXM = 1e5 + 5;
const int M = 1e5;
int p[MAXM], mu[MAXM], c[MAXM][MAXC], g[MAXM][MAXC], pw[MAXM][MAXN], s[MAXM][MAXC][MAXN];
bool vis[MAXM];
void pre()
{
mu[1] = 1;
for (int i = 2; i <= M; i++)
{
if (!vis[i])
{
p[++p[0]] = i;
mu[i] = MOD - 1;
}
for (int j = 1; j <= p[0] && i * p[j] <= M; j++)
{
vis[i * p[j]] = true;
if (i % p[j] == 0)
{
mu[i * p[j]] = 0;
break;
}
mu[i * p[j]] = mul(mu[i], mu[p[j]]);
}
}
for (int i = 0; i <= M; i++)
{
c[i][0] = 1;
for (int j = 1; j <= min(i, C); j++)
{
c[i][j] = add(c[i - 1][j], c[i - 1][j - 1]);
}
}
for (int i = 1; i <= M; i++)
{
for (int j = 1; i * j <= M; j++)
{
if (!mu[j])
{
continue;
}
for (int k = 2; k <= min(i + 1, C); k++)
{
g[i * j][k] = add(g[i * j][k], mul(c[i - 1][k - 2], mu[j]));
}
}
}
for (int i = 1; i <= M; i++)
{
pw[i][0] = 1;
for (int j = 1; j <= N; j++)
{
pw[i][j] = mul(pw[i][j - 1], i);
}
}
for (int i = 1; i <= M; i++)
{
for (int j = 2; j <= C; j++)
{
for (int k = 0; k <= N; k++)
{
s[i][j][k] = add(s[i - 1][j][k], mul(g[i][j], pw[i][k]));
}
}
}
}
int f[MAXN];
void update(int a, int b)
{
for (int i = N; i >= 0; i--)
{
f[i] = add(mul(f[i], a), mul(i ? f[i - 1] : 0, b));
}
}
int GetSum(int c, int n, int l, int r)
{
return sub(s[r][c][n], s[l - 1][c][n]);
}
const int INF = 0x3f3f3f3f;
int m[MAXN];
int main()
{
pre();
int t = read();
while (t--)
{
int n = read(), c = read(), mn = INF;
for (int i = 1; i <= n; i++)
{
m[i] = read();
mn = min(mn, m[i]);
}
int ans = 0;
for (int l = 1, r; l <= mn; l = r + 1)
{
memset(f, 0, sizeof(f));
f[0] = 1;
r = l;
for (int i = 1; i <= n; i++)
{
int k = m[i] / l;
r = min(r, m[i] / k);
update(mul(k, m[i]), sub(0, mul(mul(k, k + 1), INV)));
}
for (int i = 0; i <= n; i++)
{
ans = add(ans, mul(f[i], GetSum(c, i, l, r)));
}
}
printf("%d\n", ans);
}
return 0;
}