题解:深黯「军团」
题解:深黯「军团」
前情提要:不建议任何人写此做法,除非你想受虐。。。
题目大意:求一个排列字典序向后 \(k\) 个的逆序对和,\(n\le 5e5,k \le 1e18\)
思路
这东西一看就是数位 DP 一类的,直接开始搓。
可是数位 DP,首先第一个问题就是,如何求出字典序向后第 \(k\) 个排列,这个就有说法了。
首先考虑试填,但是当卡到这个排列的下界时贡献时不同的,所以可以设 \(g_i\) 表示到 \(i\) 位并且 \(i\) 位不动的方案数。
设 \(len_i\) 表示 \([i+1,n]\) 中,比 \(a_i\) 大的数的个数。
那么很容易就可以发现 \(g_i=g_{i+1}+len_{i+1}(n-i-1)!\)
\(g_{i+1}\) 表示后一个卡到下界的方案数,\(len_{i+1}(n-i-1)!\) 则是后面的数没有了束缚,自由的选取。
那么这样每次判断如果 \(k\le g_{i}\) 那么就说明这个排列的 \([1,i]\) 一定没有被动,设这个分界点为 \(pos\)
接下来考虑怎么找到剩下的数。
首先在 \(pos\) 这个位置时,是要特殊算一遍 \(g_{pos}\) 的,这个特判一下就好了。
然后一般情况就是从 1 一直加加加,加到那个 \(k \le (n-i)!\)。
因为现在没有束缚了,所以可以一直加加加。
但是这样是错的,为啥?因为在加的过程中会遇到已经被占用的数,所以需要判断如果这个数没被占用才能认为是加一(就是没被占用的数中的第 \(k\) 小)。
现在如果这个 \(k\) 在第一次循环的散块中,那么就已经求完了。
那么 \(k\) 非常大咋办?
很好办,发现每 \(n!\) 都会进行一个循环,所以直接模 \(n!\) 即可。
然后剩下的 \(k\) 和上面一样处理就好。
这样我们就求出了第 \(k\) 个排列是什么,设为 \(b\)。
然后考虑差分答案。
如果是散块加整块的话,后面直接跑,然后前面的块进行一个差分。
差分形式就是 \(calc(b) - calc(a - 1)\)
这个 \(a-1\) 可以直接上 \(next_permutation\),不过也有更小常数做法。
现在来考虑怎么求 \(calc\)
这个东西本质就是一个 \([1,2,3,4,5,6,\cdots, n]\) 到 \(b\) 的一个前缀和。
可以继续试填。
假设当前枚举到了 \(i\),当前数为 \(p\)
则答案可以分为 5 个部分。
第一部分是前面已经钦定的贡献,第二部分是后面的贡献,第三部分是前面对后面的贡献,第四部分是 \(p\) 对前面的贡献,第五部分是 \(p\) 对后面的贡献。
假设访问表示出现在钦定部分的数。
因为要保证复杂度,所以要将所有与的 \(p\) 放到一块计算。
先看第一部分,只需要设 \(pre\) 为当前钦定的数的逆序对个数即可。
第二部分发现是一个长度固定的全排列(可以直接离散化),所以直接预处理为 \(h_i\)。
第三部分比较恶心,注意一件事情,就是无论后面怎么变,这个贡献是固定的,因为相对位置没有改变,那么可以直接设 \(low_i\) 为当前没有出现在钦定部分的比 \(a_i\) 小的数的个数,再设 \(e=\sum_{j=1}^{i}{low_j}\),这个 \(e\) 就是这部分的贡献。
第四部分发现根本没用,因为在 \(e\) 里已经算过对前面的贡献,所以直接并入 \(e\) 就好了。
第五部分十分恶心,可以发现如果设 \(sum_i\) 表示在未访问过的数中比 \(a_i\) 小的数(本身也未访问),那么容易发现这一部分就是 \(x=\sum_{j=1}^{n}{sum_j} (j未访问)\)。
现在来把式子整理出来就是
这个式子请自行意会。
然后考虑加入一个数进入钦定会发生什么。
首先 \(pre\) 的变化很简单。
然后因为一开始将第四部分的贡献并入了 \(e\) 中,但是这不符合现在的值了,所以要减去错误(这个数已经被访问了)的在加上 \(low_i\)
再就是 \(x\) 的变化,因为这个点已经访问所以 \(sum_i=0\),然后从 \([a_i,n]\) 这所有的数,前面都会少一个数,所以都需要 - 1。
接下来只剩下一个东西了,就是咋求 \(h_i\),容易发现,如果从小到大枚举下一个数,这个数可以任意选择位置,然后贡献就是这个位置后面的数的个数,因为这个数是最大值。
这样就算完了!
分析一下复杂度竟然是 \(O(n^2)\) 的!!!!!!!!!(自行证明)
这咋办???????
优化
首先一开始那个 \(len\) 可以直接上一颗树状数组搞定。
然后那个为出现的第 \(k\) 小可以用线段树二分或平衡树搞定。
然后在 \(calc\) 的时候,\(pre\) 和 \(e\) 的变化可以使用树状数组搞定。
但是这个 \(sum\) 就需要使用线段树维护区间内值和未访问点的数量(自行思考)
这样下来复杂度就是 \(O(n\log n)\),常数我不想说了,太大了,估计在 \(20\) 以上。
代码
粘个代码,想抄直接抄吧。
using I = int;
#define int long long
using ll = __int128;
int k;
int mod, n, a[N], b[N];
ll fac[N], g[N], f[N], p[N];
bool vis[N];
class AarryTree
{
public :
int c[N];
void Update(int pos, int k)
{
for (int i = pos; i <= n; i += (i & (-i))) c[i] += k;
}
int QueryPoint(int pos)
{
int ans = 0;
for (int i = pos; i; i -= (i & (-i))) ans += c[i];
return ans;
}
int Query(int l, int r)
{
return QueryPoint(r) - QueryPoint(l - 1);
}
}AT;
#define lid id << 1
#define rid id << 1 | 1
class SemTree
{
public :
int val[N << 2], tag[N << 2];
I cnt[N << 2];
void PushDown(I id)
{
if (tag[id])
{
tag[lid] += tag[id], tag[rid] += tag[id];
val[lid] += cnt[lid] * tag[id], val[rid] += cnt[rid] * tag[id];
tag[id] = 0;
}
}
void PushUp(I id)
{
val[id] = val[lid] + val[rid];
cnt[id] = cnt[lid] + cnt[rid];
}
void Build(I id, I l, I r)
{
val[id] = tag[id] = 0;
if (l == r) return cnt[id] = 1, val[id] = l - 1, void();
I mid = (l + r) >> 1;
Build(lid, l, mid), Build(rid, mid + 1, r);
PushUp(id);
}
void UpdatePoint(I id, I l, I r, I pos)
{
if (l == r) return val[id] = cnt[id] = tag[id] = 0, void();
PushDown(id);
I mid = (l + r) >> 1;
if (pos <= mid) UpdatePoint(lid, l, mid, pos);
else UpdatePoint(rid, mid + 1, r, pos);
PushUp(id);
}
void Update(I id, I cl, I cr, I l, I r, int k)
{
if (l <= cl && cr <= r) return tag[id] += k, val[id] += cnt[id] * k % mod, void();
I mid = (cl + cr) >> 1;
PushDown(id);
if (l <= mid) Update(lid, cl, mid, l, r, k);
if (r > mid) Update(rid, mid + 1, cr, l, r, k);
PushUp(id);
}
int Query(I id, I cl, I cr, I l, I r)
{
if (l > r) return 0;
if (l <= cl && cr <= r) return val[id];
I mid = (cl + cr) >> 1;
PushDown(id);
int ans = 0;
if (l <= mid) ans = Query(lid, cl, mid, l, r);
if (r > mid) ans += Query(rid, mid + 1, cr, l, r);
return ans;
}
}ST;
class FHQ
{
public :
I ls[N], rs[N], val[N], rnk[N], tot, cnt[N], root;
I New(I x) {++tot, ls[tot] = rs[tot] = 0, val[tot] = x, rnk[tot] = rand(), cnt[tot] = 1; return tot;}
void PushUp(I id) {cnt[id] = cnt[ls[id]] + cnt[rs[id]] + 1;}
void Split(I id, I &x, I &y, I k)
{
if (!id) return (x = 0, y = 0), void();
if (val[id] > k)
{
y = id;
Split(ls[y], x, ls[y], k);
}
else
{
x = id;
Split(rs[x], rs[x], y, k);
}
PushUp(id);
}
I Merge(I x, I y)
{
if (!x || !y) return x | y;
if (rnk[x] > rnk[y])
{
rs[x] = Merge(rs[x], y);
PushUp(x);
return x;
}
else
{
ls[y] = Merge(x, ls[y]);
PushUp(y);
return y;
}
}
void Insert(I v)
{
I x, y;
Split(root, x, y, v);
// cerr << (ll)y << ' ';
I z = Merge(x, New(v));
root = Merge(z, y);
}
void Del(I v)
{
I x, y, z;
Split(root, x, y, v - 1);
Split(y, y, z, v);
root = Merge(x, z);
}
I Kth(I k)
{
I x = root;
while (1)
{
if (k == cnt[ls[x]] + 1) break;
if (k <= cnt[ls[x]]) x = ls[x];
else k -= cnt[ls[x]] + 1, x = rs[x];
}
return x;
}
}T;
ll solve(int *a, bool op)
{
memset(vis, 0, sizeof(vis));
memset(AT.c, 0, sizeof(AT.c));
ST.Build(1, 1, n);
ll ans = 0, pre = 0, low = 0, e = 0, x = 0;
for (int i = 1; i <= n; i++)
{
low = 0, x = 0;
low += a[i] - 1 - AT.Query(1, a[i]);
x = ST.Query(1, 1, n, 1, a[i] - 1);
(ans += p[n - i] * low + low * fac[n - i] % mod * (pre + e) + x * fac[n - i]) %= mod;
int tmp = AT.Query(a[i], n);
(pre += tmp) %= mod, e -= tmp;
ST.Update(1, 1, n, a[i], n, -1);
ST.UpdatePoint(1, 1, n, a[i]);
(e += low) %= mod;
AT.Update(a[i], 1);
}
return (ans + pre * op) % mod;
}
ll prefac[N];
signed main()
{
freopen("data.in", "r", stdin); freopen("data.out", "w", stdout);
freopen("army.in", "r", stdin); freopen("army.out", "w", stdout);
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> k >> mod;
for (int i = 1; i <= n; i++) cin >> a[i];
fac[0] = 1, prefac[0] = 1;
for (ll i = 1; i <= n; i++)
{
fac[i] = fac[i - 1] * i % mod;
prefac[i] = prefac[i - 1] * i;
if (prefac[i] > k) prefac[i] = k + 10;
}
for (int i = 2; i <= n; i++) p[i] = (i * p[i - 1] + (i * (i - 1) / 2ll) % mod * fac[i - 1]) % mod;
ll pos = 0, tmp = 1;
g[n] = 1;
memset(AT.c, 0, sizeof(AT.c));
for (int i = n - 1; i >= 0; i--)
{
ll len = 0;
len = AT.Query(a[i + 1], n);
tmp += len * prefac[n - i - 1];
if (tmp >= k) {pos = i + 1; break;}
g[i] = tmp;
vis[a[i + 1]] = 1;
AT.Update(a[i + 1], 1);
}
if (pos)
{
memset(vis, 0, sizeof(vis));
memset(AT.c, 0, sizeof(AT.c));
for (int i = 1; i <= n; i++) T.Insert(i);
for (int i = 1; i < pos; i++) T.Del(a[i]), vis[a[i]] = 1, AT.Update(a[i], 1), b[i] = a[i];
for (int i = pos; i <= n; i++)
{
ll tmp = 1;
if (i == pos)
{
int len = 0;
len = a[i] - AT.Query(1, a[i]);
tmp = len + 1;
k -= g[i];
}
tmp += k / prefac[n - i];
if (k % prefac[n - i] == 0) tmp--, k = prefac[n - i];
else k %= prefac[n - i];
tmp = T.Kth(tmp);
b[i] = tmp;
vis[tmp] = 1;
AT.Update(a[i], 1);
T.Del(tmp);
}
// return 0;
cout << (int)(solve(b, 1) - solve(a, 0) + mod) % mod << '\n';
return 0;
}
ll ans = p[n] - solve(a, 0), w = 0; // first
pos = 0;
k -= g[0];
w += k / prefac[n];
if (k % prefac[n] == 0) w--, k = prefac[n];
else k %= prefac[n];
(ans += p[n] * w % mod) %= mod;
for (int i = 1; i <= n; i++) a[i] = i;
tmp = 1;
g[n] = 1;
memset(vis, 0, sizeof(vis));
memset(AT.c, 0, sizeof(AT.c));
for (int i = n - 1; i >= 0; i--)
{
int len = 0;
len = AT.Query(a[i + 1], n);
tmp += len * prefac[n - i - 1];
if (tmp >= k) {pos = i + 1; break;}
g[i] = tmp;
vis[a[i + 1]] = 1;
AT.Update(a[i + 1], 1);
}
memset(vis, 0, sizeof(vis));
memset(AT.c, 0, sizeof(AT.c));
for (int i = 1; i <= n; i++) T.Insert(i);
for (int i = 1; i < pos; i++) T.Del(a[i]), vis[a[i]] = 1, AT.Update(a[i], 1), b[i] = a[i];
for (int i = pos; i <= n; i++)
{
int tmp = 1;
if (i == pos)
{
int len = 0;
len = a[i] - AT.Query(1, a[i]); // maybe O(1)
tmp = len + 1;
k -= g[i];
}
tmp += k / prefac[n - i];
if (k % prefac[n - i] == 0) tmp--, k = prefac[n - i];
else k %= prefac[n - i];
tmp = T.Kth(tmp);
b[i] = tmp;
vis[tmp] = 1;
AT.Update(a[i], 1);
T.Del(tmp);
}
cout << (int)(ans + solve(b, 1)) % mod << '\n';
return 0;
}

浙公网安备 33010602011771号