难得写篇题解之二
还是问了BUAA的dalao才算明白复杂度
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <queue>
#include <vector>
#include <cmath>
#include <map>
#include <string>
#include <iostream>
#include <queue>
#include <stack>
#define pii pair<int,int>
#define xx first
#define yy second
#define mp make_pair
#define lson l, mid, x<<1
#define rson mid+1, r, x<<1|1
#define eps 1e-9
#define uint unsigned
using namespace std;
typedef long long ll;
const int N = 3005;
int mx[N][N], mn[N][N], dp[N][N], f[N];
int a[N];
//dp[i][j]代表区间[i,j]可以被切成多少小块,每小块分别排序后可以实现区间递增
//f[i]代表[i,k](即以i为左端点的所有区间)最右的合法可分块的位置
vector< pii > ivl;
int main()
{
int n, m, i, j, k, T;
scanf("%d", &T);
while( T -- ){
scanf("%d", &n);
for( i = 1; i <= n; i ++ ) scanf("%d", a+i);
for( i = 1; i <= n; i ++ ){
for( j = i; j <= n; j ++ ){
if( i == j ) mx[i][i] = mn[i][i] = a[i], dp[i][i] = 1, f[i] = i;
else{
mx[i][j] = max( mx[i][j-1], a[j] );
mn[i][j] = min( mn[i][j-1], a[j] );
}
}
}
for( int len = 2; len <= n; len ++ ){//这里区间长度从小到大枚举
for( i = 1; i+len-1 <= n; i ++ ){
j = i+len-1;
if( mx[i][j]-mn[i][j] != j-i )
dp[i][j] = 0;
else{
if( mn[i][j] < mn[i][f[i]] ){
dp[i][j] = 1;
}
else dp[i][j] = dp[i][f[i]] + dp[f[i]+1][j];//!!!
f[i] = j;
}
}
}
ivl.clear();
for( i = 1; i <= n; i ++ ){
for( j = i; j <= n; j ++ ){
if( mx[i][j] == j && mn[i][j] == i ) break;
else continue;
}
ivl.push_back( mp( i, j ) );
i = j;
}
int ans = ivl.size(), cnt = ivl.size();
for( auto v : ivl ){
int l = v.xx, r = v.yy;
for( i = l; i < r; i ++ ){//(l,i)
if( mx[l][i]-mn[l][i] == i-l && mx[l][i] == r )
for( j = r; i < j; j -- ){//(j,r)
if( mx[j][r]-mn[j][r] == r-j && mn[j][r] == l ){
ans = max( ans, cnt+1+dp[i+1][j-1] );
}
}
}
}
printf("%d\n", ans);
}
}
/*
给你一个n的排列,执行一下操作
1.把它分成若干小块
2.将小块内的区间排序
3.最多有一次机会可以交换两个小块(交换的小块大小可以不一样)
使得得到的排列为有序的,问能分成的最多小块数
考虑如果不进行交换,可以把排列分成最多的小块数,这是可以通过扫描扫出来的
交换实质上是对一个小块内的一个前缀和后缀交换位置(如果不是前后缀,则可以切成更多小块)
对于一个块(l,r),切出前缀(l,i)和后缀(j,r)后,要计算中间区间(i+1,j-1)还可以最多被切成多少小块
这个可以预处理出来,在dp和f中
*/