冒泡排序 题解
首先一个事情就是:
每个位置必须交换一次并且只能交换一次
从这个结论里就有几个个特判:
1.应当满足n-1次交换
找逆序对
如果逆序对个数不等于n-1就直接puts(0)
一次冒泡减少一个逆序对,所以就是这样
由于\(n\leqslant 5000\)所以直接冒泡逆序对就行了
2.每个位置最多交换一次:
就是一个位置不能换两次,这个也是个小特判
这个用bool维护就可以搞一下
3.每个位置必须交换一次
这个处理显然就是看a[i]==i
解释就是如果a[i]==i这个节点不用交换
违背了每个位置必须交换1次
这三个做法一过去就该想正解了
首先给序列就肯定不是白干的:
排除了一个叫组合数学的选项
于是计数问题只剩DP了
考虑DP的话这个就比较amazing了
就是以第\(i\)位为阶段(这个挺常规的)
然后就是拓扑序上的第\(b\)个为状态?
转移就是从第\(i-1\)转移:
如果移动方向向关系为右移:
\(f[i][j]=f[i-1][j-1]+f[i][j-1]\)
否则:
\(f[i][j]=f[i][j+1]+f[i-1][j]\)
然后移动方向关系的预处理:
首先就是不在原位置上的一个数\(a[i]\)
在\(a[i]\)右边找到数\(i\)
然后就是让中间的移动关系都向右
顺便打上2特判的标记
最后让\(i-1\)打上左移
甚至可以滚动数组再压一位
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int o=5555,mod=1e9+7;
int n,ans,cnt;
int a[o],f[o],g[o];
char s[o];
bool u[o];
void in(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
a[i]++;
}
}
void pre(){
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(a[i]>a[j]){
cnt++;
}
}
}
if(cnt!=n-1){
cout<<0;
exit(0);
}
for(int i=1;i<=n;i++){
if(a[i]^i){
for(int j=i+1;j<=n;j++){
if(a[j]==i){
for(int k=i;k<j;k++){
if(u[k]){
cout<<0;
exit(0);
}
u[k]=1;
s[k]='>';
}
if(i>1){
s[i-1]='<';
break;
}
}
}
}
}
}
void work(){
n--;
f[1]=1;
for(int i=2;i<=n;i++){
fill(g,g+i+1,0);
if(s[i-1]=='<'){
for(int j=1;j<=i;j++){
g[j]=(g[j-1]+f[j-1])%=mod;
}
}
else{
for(int j=i;j>0;j--){
g[j]=(g[j+1]+f[j])%=mod;
}
}
swap(f,g);
}
}
void out(){
for(int i=1;i<=n;i++){
ans=(ans+f[i])%mod;
}
cout<<ans;
}
int main(){
freopen("mp.in","r",stdin);
freopen("mp.out","w",stdout);
in();
pre();
work();
out();
return 0;
}

浙公网安备 33010602011771号