AtCoder Grand Contest 054

C.Roughly Sorted

题目描述

如果一个排列每个位置上的逆序对个数都 \(\leq k\),那么它是好排列。假设你有排列 \(P\),每次可以交换两个相邻元素,用最小的步数得到好排列 \(P'\)

现给定 \(P'\)\(k\),求可能的 \(P\) 有多少个。

\(n\leq 5000\)

解法

首先考虑知道了排列 \(P\) 怎么求出排列 \(P'\),设第 \(i\) 个位置的逆序对个数是 \(x_i\),因为一次交换最多让逆序对减一,所以答案下界是 \(\sum_i\max(x_i-k,0)\),构造方法是每次找到最小的位置 \(i\) 满足 \(p_{i-1}>p_i\),并且 \(x_i>k\),把这两个位置交换一下,可以证明如果排列不合法一定能找到这样的位置。

那么考虑用排列 \(P'\) 还原出可能的 \(P\),首先我声明:如果我们知道了每个位置上的逆序对个数,那么对应的排列是唯一的。所以我们考察调整后的逆序对个数是否合法即可。

对于 \(x_i<k\) 的位置是不能主动换的,因为交换让逆序对增加或减小都是不合法的,让他们的逆序对固定即可。对于 \(x_i=k\) 的位置逆序对是可以任意增加的,但就是不能减小,所以如果我们按照 \(i=n...1\) 的顺序考虑,我们可以把它右移任意步数,那么乘上 \((n-i+1)\) 即可。

总结

最优策略问题也可以思考构造答案下界,但是首先一定要想清楚答案下界是什么。

计数题中的转化要建立起对应关系,比如这题我们建立了逆序对和排列的对应关系,就把问题转化到逆序对上了。

#include <cstdio>
const int M = 5005;
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 n,k,ans=1,p[M],x[M];
signed main()
{
	n=read();k=read();
	for(int i=1;i<=n;i++)
		p[i]=read();
	for(int i=1;i<=n;i++)
		for(int j=1;j<i;j++)
			x[i]+=(p[j]>p[i]);
	for(int i=n;i>=1;i--)
		if(x[i]==k) ans=1ll*ans*(n-i+1)%MOD;
	printf("%d\n",ans); 
}
posted @ 2021-07-23 16:48  C202044zxy  阅读(125)  评论(0编辑  收藏  举报