P7406 [JOI 2021 Final] 集合写真
题意
给定一个排列,每次可以交换相邻两个,最少要多少次操作使得
- 对于任意 \(i \in [1,N-1]\),都有 \(a_i <a_{i+1}+2\)。
题解
- 经过观察可以发现,连续下降的数之间只能相差1,也就是说如果有连续下降的一段,它们必定是依次递减1。
- 所以一个合法的序列一定可以这样划分,我们将整个序列划分成若干个不相交的连续下降段,其中每个段的最小值(右端点)一定大于前一个段的最大值(左端点)。 也就是形如(3 2 1)(4) (7 6 5)
- 设\(f[i]\)表示值域在1-i之间的数已经排好的最小代价,\(f[i]=f[j-1]+cost(j,i)\)。
- 注意到我们在将值域在\([1,j-1]\)的数归位并不会影响值域在\([j,n]\)之间的数的相对顺序
- 将值域在\([j,i]\)的数归位可以看作这样两个步骤,先将所有值域在\([j,i]\)的数移动到\([j,i]\)的位置范围,但是两个值域在\([j,i]\)不会交换,这一部分的cost就是\(\displaystyle \sum_{l=i}^j \sum_{r=j+1}^{n} pos[i]>pos[j]\)
- 而第二部分则是交换值域在\([j,i]\)的数,cost为\(\displaystyle \sum_{l=i}^j \sum_{r=l+1}^{n} pos[i]<pos[j]\)
- 具体实现的话可以利用二维前缀和,考虑每一对\((i,j)\)会对哪些区间进行贡献,然后进行利用二维差分即可。
#include<bits/stdc++.h>
#define fo(i,a,b) for (ll (i)=(a);(i)<=(b);(i)++)
#define fd(i,b,a) for (ll (i)=(b);(i)>=(a);(i)--)
#define eb emplace_back
#define pi pair<ll,ll>
#define mk(x,y) make_pair((x),(y))
#define lc (o<<1)
#define rc ((o<<1)|1)
//#define A puts("Yes")
//#define B puts("No")
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef double db;
const ll mo1=1e9+7;
const ll mo2=1e9+9;
const ll P=131;
const ll Q=13331;
const ll inf=1ll<<60;
const int N=5e3+5;
ll f[N],a[N],pos[N],n;
struct pre{
ll s[N][N];
pre(){
memset(s,0,sizeof(s));
}
void upd(int a,int b,int c,int d){
s[a][b]++;
s[a][d+1]--;
s[c+1][b]--;
s[c+1][d+1]++;
}
void work(){
fo(i,1,n) fo(j,1,n) s[i][j]+=s[i][j-1];
fo(j,1,n) fo(i,1,n) s[i][j]+=s[i-1][j];
}
};
int main() {
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
pre s,t;
scanf("%lld",&n);
fo(i,1,n) {
scanf("%lld",&a[i]);
pos[a[i]]=i;
}
fo(i,1,n) fo(j,i+1,n) if (pos[i]>pos[j]) {
s.upd(1,i,i,j-1);
// printf("%d %d\n",i,j);
}
fo(i,1,n) fo(j,i+1,n) if (pos[i]<pos[j]) t.upd(1,j,i,n);
// return 0;
s.work();
t.work();
fo(i,1,n) f[i]=inf;
fo(i,1,n) fo(j,1,i) {
f[i]=min(f[i],f[j-1]+s.s[j][i]+t.s[j][i]);
}
printf("%lld",f[n]);
return 0;
}

浙公网安备 33010602011771号