【JZOJ 省選模擬】6691.六道劍「一念無量劫」

題目

Description
妖夢在練習劍術
有 n 個木樁排成一排,從左到右高度分別爲 h 1 ,h 2 ,h 3 ,…,h n ,這些高度兩兩不同
妖夢每次可以選擇兩個相鄰的木樁交換,這樣的交換可以進行任意多次
妖夢也可以使用符卡:選擇兩個木樁交換,但最多隻能使用一次。
妖夢想要知道將木樁排成從左到右遞增的順序,她最少需要進行多少次交換。

Input
從 sword.in 中讀入數據
第一行一個正整數 n
第二行 n 個正整數 h 1 ,h 2 ,…,h n

Output
輸出到文件 sword.out 中
一行一個整數,表示最少的交換次數

Sample Input
5
3 5 4 1 2

Sample Output
5

Data Constraint
在這裏插入圖片描述

Hint
(3,5,4,1,2)
交換 1,2 (3,5,4,2,1)
交換 3,4 (3,5,2,4,1)
交換 3,5 (5,3,2,4,1)
交換 2,3 (5,2,3,4,1)
交換 1,5 (1,2,3,4,5)
可以證明這是次數最少的方法

思路

容易發現最優解滿足 h[i] 是前綴最大值且 h[j] 爲後綴最小值
不妨反過來考慮每個 x 對哪些 (i,j) 有貢獻
令 l 爲最小的滿足 h[l]>h[x] 且 h[l] 爲前綴最大值的 l,h[r] 爲最大
的滿足 h[r]<h[x] 且 h[r] 爲後綴最小值的 r
那麼 x 的貢獻就是 i ∈ [l, x − 1], j ∈ [x + 1,r] 這樣一個矩形
問題變爲矩形加全局最大值,離線掃描線維護即可
複雜度 O(n log n)

代碼

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define N 300005
int L,R,n,yjy,g,ta,tb,cnt,a[N],A[N],B[N],s[N],t[N];
void add(int x,int p) {
    for(; x <= n; x += x & -x) s[x] += p;
}
int que(int x) {
    int g = 0;
    for(; x; x -= x & -x) g += s[x];
    return g;
}
int F(int x,int y) {
    while(R < y) add(a[++R],1);
    while(R > y) add(a[R--],-1);
    while(L < x) add(a[L++],-1);
    while(L > x) add(a[--L],1);
    return que(a[x]) + que(a[x] - 1) - que(a[y]) - que(a[y] - 1) - 2;
}
void work(int l,int r,int ql,int qr) {
    if (l > r || ql > qr)
        return;
    int mid = l + r >> 1,p = 0,g = -1e18,t;
    for(int i = ql; i <= qr; i++)
        if (A[i] != B[mid])
            if (g < (t = F(A[i],B[mid])))
                g = t,p = i;
    yjy = max(yjy,g);
    work(l,mid - 1,ql,p);
    work(mid + 1,r,p,qr);
}
signed main() {
	freopen("sword.in","r",stdin); freopen("sword.out","w",stdout);
    scanf("%lld",&n);
    bool bz=1;
    for(int i = 1; i <= n; i++)
    {
    	scanf("%lld",&a[i]),t[i] = a[i];
    	if(a[i]<a[i-1]) bz=0;
	}
	if(bz)
	{
		printf("0"); return 0;
	}
    sort(t + 1,t + 1 + n);
    cnt = unique(t + 1,t + 1 + n) - t - 1;
    for(int i = 1; i <= n; i++) a[i] = lower_bound(t + 1,t + 1 + cnt,a[i]) - t;
    for(int i = 1; i <= n; i++) g += i - 1 - que(a[i]),add(a[i],1);
    if (!g) {
        puts(cnt == n ? "1" : "0");
        return 0;
    }
    for(int i = 1; i <= n; i++)
        if (!ta || a[i] > a[A[ta]])
            A[++ta] = i;
    for(int i = n; i >= 1; i--)
        if (!tb || a[i] < a[B[tb]])
            B[++tb] = i;
    reverse(B + 1,B + tb + 1);
    L = 1,R = 0;
    yjy = -1e18;
    memset(s,0,sizeof(s));
    work(1,tb,1,ta);
    printf("%lld\n",g - yjy);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章