A
B

记录一个神秘做法

题面:[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;
}
posted @ 2025-08-16 16:35  MyShiroko  阅读(46)  评论(8)    收藏  举报