记录一个神秘做法
题面:[HZOI]CSP-S模拟12 & 多校2025暑假集训模拟赛8 T3
大意简述:给定一个长度为 \(n\) 的序列,给定 \(k\) 与 \(mod\) 。求该序列 \(k\) 个下个排列的逆序对求和。
赛时看了一眼题,嗯。。。
如果暴力枚举每个序列的话肯定 \(TLE\) 。因为 \(k \in [1,1e18]\) ,所以直接放弃暴力,开始乱搞。(赛后事实证明乱搞跟暴力分一样高)
开始手膜样例。
自己手膜了 \(n = 5\) 的全排列,每个序列都求了一次逆序对(真不知道我是怎么膜下来的)。
一部分:
// 逆序对个数
// n = 1的全排列:1 0
//
// n = 2的全排列:1 2 0
// 2 1 1
//
// n = 3的全排列:1 2 3 0
// 1 3 2 1
// 2 1 3 1
// 2 3 1 2
// 3 1 2 2
// 3 2 1 3
//
// n = 4的全排列:1 2 3 4 0
// 1 2 4 3 1
// 1 3 2 4 1
// 1 3 4 2 2
// 1 4 2 3 2
// 1 4 3 2 3
// 2 1 3 4 1
// 2 1 4 3 2
// 2 3 1 4 2
// 2 3 4 1 3
// 2 4 1 3 3
// 2 4 3 1 4
// 3 1 2 4 2
// 3 1 4 2 3
// 3 2 1 4 3
// 3 2 4 1 4
// 3 4 1 2 4
// 3 4 2 1 5
// 4 1 2 3 3
// 4 1 3 2 4
// 4 2 1 3 4
// 4 2 3 1 5
// 4 3 1 2 5
// 4 3 2 1 6
然后发现有规律:
貌似可以预处理出每个长度为 \(i\) 的逆序对个数 \(f_i\) 。(此时这个蒟蒻还没有意识到他看错了题)
那么怎么求呢?
发现 \(f_i = f_{i - 1} * i + g_{i - 1} * t_{i - 1}\)
那 \(g\) 和 \(t\) 又是什么呢?
\(g_i\) 为 \(1\) 到 \(i\) 的阶乘。
\(t_i\) 为 \(1\) 到 \(i\) 的和。
怎么说,是不是还挺好想的。
然后直接处理出 \(k\) 个序列的逆序对总和,非常完美。
inline void init()
{
f[1] = 0;
g[1] = 1;
t[1] = 1;
for(int i = 2;i <= n;i ++)
{
t[i] = (t[i - 1] + i) % mod;
g[i] = g[i - 1] * i;
f[i] = (f[i - 1] * i % mod + g[i - 1] * t[i - 1] % mod) % mod;
}
}
inline int solve(int skk)
{
if(skk == 0) return 0;
int res = 0;
int op = skk / g[n];
res = op * f[n] % mod;
skk %= g[n];
for(int i = n - 1,tim;i >= 1;i --)
{
tim = skk / g[i];
skk %= g[i];
res += t[tim - 1] * g[i] + f[i] * tim;
res += skk * tim;
}
return res;
}
好的,现在让我们试一下样例。。。
嗯?怎么第一个都过不了?
难道是我写假了?
补怼,这个 \(a\) 数组我怎么没用到啊?(这时候才后知后觉地发现读错题了。。。)
怎么办。。。
诶,要不用差分吧,先求出 \(a\) 数组在长度为 \(n\) 的全排列中字典序的排名 \(jk\) ,然后差分一下,用 \(solve(k + jk - 1) - solve(jk - 1)\) 不就行了嘛。
我真是个天才。
那么问题来了,怎么求这个排名呢?
在考场上没有想出小于 \(O(n ^ 2)\) 的做法(之后发现可以优化到 \(O(n log{n})\) ),于是写了 \(n ^ 2\) 的做法。
总之就是写出来了,试了前4个样例以后,发现过的十分顺滑。
那来试试大样例吧!
欸,怎么没过。。。
肯定是查排名的锅,造一组小一点的数据试试。
然后爽调了半小时。
哎呀哎呀,还有两分钟结束了,怎么办?
欸,这个 \(g\) 数组怎么老不对,是不是取模的问题?
试试去掉取模。。。欸,造的样例过了!赶紧交赶紧交。
总之是在结束前两分钟内提交成功了。
但是 \(n\) 的范围是 \(5e5\) 。那只能拿部分分了。。。
最后喜提20,跟暴力一个分。
但是代码正确性是对的,(因为过的是跟暴力不一样的subtask),错只是因为阶乘存不下而已。。。
感觉思路挺清奇的,故记录之。
理论时间复杂度: \(O(n log{n})\)
理论空间复杂度: \(?\)
下面放上完整代码。
#include<bits/stdc++.h>
// #define getchar_unlocked getchar
// #define putchar_unlocked putchar
#define ent putchar_unlocked('\n')
#define con putchar_unlocked(' ')
#define int __uint128_t
#define Blue_Archive return 0
using namespace std;
const int N = 5e5 + 3;
int n;
int k;
int mod;
int ans;
int top;
int a[N];
int f[N];
int g[N];
int t[N];
bool vis[N];
inline int read()
{
int x = 0,f = 1;
char c = getchar_unlocked();
while(c < '0' || c > '9')
{
if(c == '-') f = -1;
c = getchar_unlocked();
}
while(c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0',c = getchar_unlocked();
return x * f;
}
inline void write(int x)
{
if(x < 0) putchar_unlocked('-'),x = -x;
if(x > 9) write(x / 10);
putchar_unlocked(x % 10 + '0');
}
inline void init()
{
f[1] = 0;
g[1] = 1;
t[1] = 1;
for(int i = 2;i <= n;i ++)
{
t[i] = (t[i - 1] + i) % mod;
g[i] = g[i - 1] * i;
f[i] = (f[i - 1] * i % mod + g[i - 1] * t[i - 1] % mod) % mod;
}
}
inline int find()
{
if(n == 0) return 0;
int res = 0;
vis[a[1]] = 1;
res += (a[1] - 1) * g[n - 1] % mod;
for(int i = 2;i <= n;i ++)
{
for(int j = 1;j < a[i];j ++)
{
if(vis[j]) continue;
res += g[n - i];
}
vis[a[i]] = 1;
}
return res + 1;
}
inline int solve(int skk)
{
if(skk == 0) return 0;
int res = 0;
int op = skk / g[n];
res = op * f[n] % mod;
skk %= g[n];
for(int i = n - 1,tim;i >= 1;i --)
{
tim = skk / g[i];
skk %= g[i];
res += t[tim - 1] * g[i] + f[i] * tim;
res += skk * tim;
}
return res;
}
signed main()
{
// freopen("3.in","r",stdin);
// freopen("3.out","w",stdout);
freopen("army.in","r",stdin);
freopen("army.out","w",stdout);
n = read();
k = read();
mod = read();
init();
for(int i = 1;i <= n;i ++) a[i] = read();
int jk = find();
ans = solve(k + jk - 1) - solve(jk - 1);
write((ans % mod + mod) % mod);ent;
Blue_Archive;
}

浙公网安备 33010602011771号