P9159 「GLR-R4」大暑 题解
YeahPotato · · 题解
同学打月赛后告诉我有道很离谱的题,遂做,确实挺离谱的,这里讲一下自己做的思路。
题意
- 给定
n ,平面直角坐标系上有点(0,0)\sim(0,n-1),(1,0)\sim(1,n-1) 。对于一个n 排列p ,对每个i 连线段(0,i)-(1,p_i) 。 - 考虑从每个横坐标为
0 的点出发,依附于这些线段各走一条折线(或线段)到达一个横坐标为1 的点,这些折线(或线段)仅能在原先n 条线段的交点上重合。 - 求所有
p 的方案数之积{}\bmod 335~544~323 。 -
Step 1
方便起见称原题中完美匹配(简称匹配)的连线为 “线段”,染色的折线(或线段)为 “路径”。
模拟一些例子,发现三点:
- 路径不会 “往回折” 且线段没有任何部分不被经过。
- 选择一条路径在(最后)一个交点的走向,剩余的东西并没有本质不同,感性地说像是有一个乘法原理。
- 三线段交于同一点和三线段交于三个点的方案数是不同的,也就是说这个几何背景是有用的。
可以证明第一点:考虑任一横坐标
进一步分析,发现一个 “
显然贡献值只取决于该交点最多被几条线段经过(显然这些线段的端点均不同,所以相当于是一个选择问题),故可以求出
Step 2
求
(为了美观横过来了)由图中相似关系可见,两边选的相邻点距离要对应成比例。
第一反应是直接硬着对极大共点选择(一些线段交于同一点,且不能再多选一条经过该交点的线段。显然这些线段的端点均不同)计数,官方解法有更方便的容斥做法,这里就算给一个备选方案了。极大共点选择的条件为:
- 两边选的点纵坐标分别成等差数列;
- 不能再往左右扩展;
- 公差互质。
首先发现互质条件无法直接处理,因此莫反,枚举公差的公因数
那么只需对于每个
Step 3
直接求
二项式反演得:
然后就会发现没法快速求出 (如果有大佬会的能教一下吗/kel
Step 4
既然没法快速求
则:
而
两次差卷积即可。细节略去。
Step 5
最后一个问题是
一个性质是
最终时间复杂度为
代码
一个优化:两次卷积分别要求类似于
void NTT(int n, int *a, int o) {
for (int i=0; i<n; i++)
if (i < f[i]) swap(a[i], a[f[i]]);
for (int i=1, _; i<n; i<<=1)
for (int j=0; j<n; j+=i<<1)
for (int k=j; k<j+i; k++)
_ = 1ll * g[i][k-j] * a[k+i] % P, a[k+i] = R(a[k], P - _), a[k] = R(a[k], _);
if (o) {
reverse (a+1, a+n);
for (int i=0; i<n; i++)
a[i] = 1ll * a[i] * I % P;
}
}
int main() {
cin >> n;
for (int i=mu[1]=1; i<=n; i++) if (mu[i])
for (int j=i<<1; j<=n; j+=i)
mu[j] -= mu[i];
for (int i=1; i<n; i++)
L[i] = (n - 1) / i + 1, C[i] = (n - 1) % i + 1;
for (int g=1; g<n; g++) {
if (! mu[g]) continue;
int m = 0, sl = 0, sc = 0;
for (int i=g; i<n; i+=g) {
if (m && l[m-1] == L[i]) c[m-1] = R(c[m-1], C[i]);
else if (l[m] == L[i]) c[m] = R(c[m], C[i]);
else l[++m] = L[i], c[m] = C[i];
if (C[i] < i && L[i] > 2)
if (l[m] == L[i] - 1) c[m] = R(c[m], i - C[i]);
else l[++m] = L[i] - 1, c[m] = i - C[i];
}
for (int i=1; i<=m; i++) {
w[l[i]] = (w[l[i]] + ((sl + 1ll * (P - l[i] + 1) * sc << 1) + c[i]) % P * (~ mu[g] ? c[i] : P - c[i])) % P;
d[l[i]-1] = (d[l[i]-1] + (2ll * sc + c[i]) * (~ mu[g] ? c[i] : P - c[i])) % P;
sl = (sl + 1ll * l[i] * c[i]) % P, sc = R(sc, c[i]);
}
}
for (int i=n; i>1; i--)
d[i] = R(d[i], d[i+1]), w[i] = R(w[i], R(d[i], d[i]));
for (int i=F[0]=1; i<=n; i++)
F[i] = 1ll * F[i-1] * i % P;
IF[n] = qpow(F[n], P - 2);
for (int i=n-1; ~i; i--)
IF[i] = 1ll * IF[i+1] * (i+1) % P;
while (m < (n << 1) - 3) m <<= 1;
I = qpow(m, P - 2);
for (int i=0; i<m; i++)
f[i] = f[i>>1] >> 1 | (i & 1 ? m >> 1 : 0);
for (int i=1; i<m; i<<=1) {
g[i] = new int [i] {1};
if (i > 1) {
g[i][1] = qpow(3, (P - 1) / (i << 1));
for (int j=2; j<i; j++) g[i][j] = 1ll * g[i][j-1] * g[i][1] % P;
}
}
for (int i=0; i<n-1; i++)
a[i] = 1ll * F[n-i] * w[n-i] % P, b[i] = IF[i];
NTT(m, a, 0), NTT(m, b, 0);
for (int i=0; i<m; i++)
a[i] = 1ll * a[i] * b[i] % P;
NTT(m, a, 1);
for (int i=0; i<m; i++)
a[i] = i < n - 1 ? 1ll * F[i] * a[i] % P : 0;
for (int i=0; i<m>>1; i++)
swap(b[i], b[i|m>>1]);
NTT(m, a, 0);
for (int i=0; i<m; i++)
a[i] = 1ll * a[i] * b[i] % P;
NTT(m, a, 1);
for (int i=0; i<n-1; i++)
c[n-i] = 1ll * IF[n-i] * a[i] % P;
for (int i=2; i<=n; i++)
if ((i & n) == i & n - i - 1 & 1 ^ c[i] & 1)
c[i] += P;
for (int i=n; i>1; i--) {
if ((c[i] += c[i+1]) >= P2 - 1) c[i] -= P2 - 1;
ans = 1ll * ans * qpow_(i, c[i]) % P2;
} cout << ans;
}