P4769 [NOI2018] 冒泡排序
作者:互联网
题意
定义一个排列是好的,当且仅当对它冒泡排序时交换次数是下界:
\[\dfrac{1}{2}\sum_{i=1}^n|p_i-i| \]给定一个长度为 \(n\) 的排列 \(p\),求在所有的长度为 \(n\) 的排列中有多少字典序大于 \(p\) 的好的排列。
Solution
这是上课 \(\texttt{M}\color{red}{\texttt{r_Spade}}\) 讲 dp 时的例题捏~
首先考虑如果没有 \(p\) 的限制,如何计算长度为 \(n\) 的好的排列的个数?首先考虑这个交换的下界,这东西是通过每个元素与其应该在的位置之差,换句话说,我们在进行冒泡排序的时候,必然要使得每个元素都朝着自己的目的地前进,否则必然没有达到这个下界。
这个性质显然远远不足以用来做题,于是你考虑这样一个排列还有什么性质。鉴于冒泡排序实际上是逐一地消除逆序对,所以我们只要使得消除的过程中不会反复横跳,即如果一个元素在它应该在的位置之前,则它之前不能有比它大的元素,如果在它应该在的位置之后,则它之后不能有比它小的元素。
直接利用这个东西也显得较为困难,那我们考虑如果一个不好的序列,可能会有什么性质。假如说有一个数,它在其位置之前,并且它前面有一个数比它大,那么它后面肯定有比它小的数;同样的,如果在其位置之后,并且它后面又一个数比它小,那么它前面肯定又比它大的数。这样的话,一个不好的排列,必然存在一个长度为 \(3\) 的下降子序列,这是必要的。好那我们来看看是否是充分的。假如一个排列中存在一个长度为 \(3\) 的下降子序列,那么容易发现无论中间那个数是在其位置前还是后都是不合法的,所以易得这东西也是充分的。
也就是说,不存在一个长度为 \(3\) 的下降子序列是一个排列是好的的充要条件。
依据于此,我们可以进行一个 dp。看到 \(6\times 10^5\) 的数据,肯定是从一个前缀中转移过来,于是我们考虑一下令 \(dp_i\) 表示长度为 \(i\) 的好的排列的个数。现在来分析其转移。由于受到了不能出现长度为 \(3\) 的下降子序列的限制,所以我们来看看如果出现了长度为 \(3\) 的下降子序列,那么会怎么样:
- 如果这个子序列中有 \(0\) 或者 \(3\) 个元素出现在当前的前缀中,那么这和我们当前的选择没有关系,所以不受限制;
- 如果这个子序列中有 \(2\) 个元素出现在当前的前缀中,那么为了避免接下来出现长度为 \(3\) 的下降子序列,需要满足此后所有的元素都大于当前元素;
- 如果这个子序列中有 \(1\) 个元素出现在当前的前缀中,那么为了避免接下来不合法,需要满足当前的最大值不与后面的任意两个元素构成一个下降子序列,这意味着我们需要记录当前的最大值。
于是我们令 \(dp_{i,j}\) 表示长度为 \(i\) 的前缀中最大值为 \(j\) 的合法方案数。那么考虑转移。考虑新加入的数与 \(j\) 的大小关系。
- 如果新加入的数 \(>j\),这就意味着 \(j\) 改变了,并且这个东西满足了上面所有的限制,可以随便转移并且不会产生新的下降序列,这是好的。
- 如果新加入的数 \(<j\),这意味着 \(j\) 没有变,但是我们注意上面的限制中要求后面的所有数都大于当前数。所以此时我们必须要满足当前数是未填的最小的那个数。但是!我们还需要注意,如果 \(i=j\),后面其实是没有小于 \(j\) 的数,所以这个转移是不成立的。
也就是说,\(dp_{i,j}\) 可以转移到 \(dp_{i+1,k}\),\(k\ge j\),如果 \(i=j\) 则等号不能取。我们在坐标轴上观察这个转移,发现和卡特兰数的推导简直一模一样,同样是只能向上或者向右,同样不能超过对角线。于是我们得到:
\[dp_{n,n}={2n\choose n}-{2n\choose n-1} \]也就是长度为 \(n\) 的好的排列个数就是 \(n\) 的卡特兰数。
接下来考虑字典序大于 \(p\) 这一限制。按照套路,我们一般会去枚举从哪个位置开始当前排列大于 \(p\)。假如说我们枚举到的公共前缀是 \(i\in [0,n)\)。即 \([1,i]\) 中我们的排列 \(q=p\),而在 \(i+1\) 处我们必须填入一个大于 \(p_{i+1}\) 的数,后面的随意。既然随意,只要前面合法,后面可以完全仿照上面去分析,所以我们先考虑前面合法的条件。
要判断 \([1,i]\) 是否合法,可以利用上面的限制,即如果最大值更新了,那不要紧。如果最大值没有更新,那么加入的必须是未加入的数中的最小的,这个东西随便拿一个 set
或者其它 DS 都能维护。
然后考虑位置 \(i+1\) 受到的限制。分两种情况来讨论,我们令 \(i\) 前缀中最大值为 \(mx\),则:
- \(mx<p_{i+1}\),那么只要取当前位大于 \(p_{i+1}\),也就同时更新了 \(mx\),时时合法;
- \(mx>p_{i+1}\),那么如果我们取了 \((p_{i+1},mx)\) 的数,那么显然后面会有出现比 \(p_{i+1}\) 小的数,那么就出现了长度为 \(3\) 的下降子序列,就寄了。
换句话说,我们当前的数必须要取比 \(i+1\) 前缀中最大值还大的数。这样一来 \(i+1\) 位置取的数就变成了前缀中的最大值,也就是 \(dp\) 状态中的第二位。即如果 \(p\) 中前缀 \(i+1\) 的最大值是 \(mx'\),那么可以理解成我们在 \(dp\) 转移的时候前面的方案都只有一种(因为被我们钦定完了),然后到这里,我们把 \(dp_{i+1,mx'+1\sim n}\) 都令为 \(1\),然后接下来就是像上面一样的自由转移!也就是说,当前位置最大值只可能是 \([mx'+1,n]\),并且一定存在这样一种方案(只要前缀是合法的)。
我们端详这个东西,可以发现这东西和 \(dp_{i,mx'+1}=1\) 为初始状态开始转移的结果是一样的,那问题就转化成从位置 \((i,mx'+1)\) 开始到达 \((n,n)\) 的只向上或者向右并且不超过 \(y=x\) 的方案数。和卡特兰数一样推导,得到:
\[{2n-i-j\choose n-j}-{2n-i-j\choose n-1-j} \]Code
#define int long long
using namespace std;
const int MAXN=2e6+10;
const int MOD=998244353;
int ksm(int a,int p){
int ret=1;while(p){
if(p&1) ret=ret*a%MOD;
a=a*a%MOD; p>>=1;
}return ret;
}int inv(int x){return ksm(x,MOD-2);}
int fac[MAXN],ifac[MAXN];
void init(){
fac[0]=ifac[0]=1;
rep(i,1,MAXN-9) fac[i]=fac[i-1]*i%MOD;
ifac[MAXN-9]=inv(fac[MAXN-9]);
per(i,MAXN-10,1) ifac[i]=ifac[i+1]*(i+1)%MOD;
}
int C(int n,int m){
if(n<0||m<0) return 0;
if(m>n) return 0;
return fac[n]*ifac[m]%MOD*ifac[n-m]%MOD;
}
int Cat(int i,int j,int n){return ((C(2*n-i-j,n-j)-C(2*n-i-j,n-1-j))%MOD+MOD)%MOD;}
int p[MAXN];
void solve(){
int n;cin>>n;
rep(i,1,n) cin>>p[i];
set<int> s;
rep(i,1,n) s.insert(i);
int mx=0,ans=0;
rep(i,0,n-1){
if(i){
if(p[i]>mx) mx=p[i];
else if(p[i]!=*s.begin()) break;
s.erase(p[i]);
}int m=max(mx,p[i+1]);
ans=(ans+Cat(i,m+1,n))%MOD;
}cout<<ans<<'\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);init();
int T;for(cin>>T;T--;)solve();
return 0;
}
标签:前缀,int,P4769,冒泡排序,序列,NOI2018,mx,dp,MOD 来源: https://www.cnblogs.com/ZCETHAN/p/16526676.html