[NOI2018] 冒泡排序

一、题目

点此看题

二、解法

因为 \(\frac{1}{2}\sum_{i=1}^n|i-p_i|=\sum_{i=1}^n\max(i-p_i,0)\),我们可以考虑后者。

\(d_i\) 表示位置 \(i\) 的逆序对数,显然有 \(d_i\geq \max(i-p_i,0)\),因为如果 \(p_i\leq i\),那么前面比 \(p_i\) 大的数至少有 \(i-p_i\) 个。

这说明必须每个位置都要满足 \(d_i=\max(i-p_i,0)\) 才是好排列,这等价于对于所有位置 \(i\),要么 \(p_i\) 是前缀最大值,要么所有比 \(p_i\) 小的数都在 \([1,i)\) 中出现过了

得到这个结论之后我们不考虑字典序限制,设 \(dp[i][j]\) 表示填了前 \(i\) 个位置最大值是 \(j\),那么转移就是填上一个更大的值,或者是填上前面所有数的 \(\tt mex\),所以转移的形式其实很简洁:

\[dp[i][j]=\sum_{k=0}^{j} dp[i-1][k] \]

此外还需要保证 \(i\leq j\),我们可以把它改写成前缀和的形式:

\[sum[i][j]=sum[i-1][j]+sum[i][j-1] \]

考虑转移的组合意义,这相当于在二维平面上行走,从 \((0,0)\) 开始,每次可以向上走或者向右走,要求不越过 \(y=x\),最后走到 \((n,n)\) 的方案数。显然这是卡特兰数,表达式是 \({2n\choose n}-{2n\choose n+1}\)


显然考虑字典序限制,显然的方法是保证前缀相同,下一个位置 \(=p_i+1\),后面位置随意填的排列数。

所以我们在保证前缀 \(i-1\) 合法的情况下,从 \((i-1,\max_{j=1}^ip_j+1)\) 开始行走到 \((n,n)\) 的方案数。我们已知从 \((x,y)\) 行走到 \((n,n)\) 的方案数是 \({2n-x-y\choose n-x}-{2n-x-y\choose n-x+1}\)

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 2000005;
#define int long long
const int MOD = 998244353;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n,m,ans,fac[M],inv[M],b[M];
void init(int n)
{
	fac[0]=inv[0]=inv[1]=1;
	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
	for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=2;i<=n;i++) inv[i]=inv[i]*inv[i-1]%MOD;
}
int C(int n,int m)
{
	if(n<m || m<0) return 0;
	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int walk(int x,int y,int n,int m)
{
	if(x>n || y>m || x<0 || y<0) return 0;
	return C(n+n-x-y,n-x)-C(n+n-x-y,n-x+1);
}
void add(int x)
{
	for(int i=x;i<=n;i+=(i&-i)) b[i]++;
}
int ask(int x)
{
	int r=0;
	for(int i=x;i>0;i-=(i&-i)) r+=b[i];
	return r;
}
void work()
{
	n=read();ans=0;
	for(int i=1;i<=n;i++) b[i]=0;
	for(int i=1,mx=0,fl=1;i<=n;i++)
	{
		int x=read();mx=max(mx,x);
		if(fl) ans=(ans+walk(i-1,mx+1,n,n))%MOD;
		add(x);
		if(x!=mx && ask(x)<x) fl=0;
	}
	printf("%lld\n",(ans+MOD)%MOD);
}
signed main()
{
	T=read();init(2e6);
	while(T--) work(); 
}
posted @ 2022-03-03 17:27  C202044zxy  阅读(52)  评论(0编辑  收藏  举报