CF820D

思惟題

題意:

給出一個\(n\)元素排列\(p[]\),定義數組\(p[]\)的偏差值爲\(\sum\limits_{i=1}^{i=n} |p[i]-i|\).每次操做都把下標爲\(n\)的數放到下標爲\(1\)的位置,其餘數依次右移,問在經過幾回操做後能使得偏差值最小c++

較麻煩作法:差分

正解是差分,設\(d[i]\)爲i次操做後的偏差值,考慮\(p[j]\)\(d[i]\)的貢獻,發現i是一段連續的區間,即區間\([l,r]\)同時加上某個等差數列。git

區間\([l,r]\)加上\(k*(x-l)+b\)\((l≤x≤r)\) 設置兩個數列\(d[i],f[i]\)數組

  1. \(d[l]+=b,d[r+1]-=b\).剩下\(k*(x-l)\).
  2. \(f[l+1]+=k. f[r+1]-=k\)\(f\)求一次前綴和,
  3. \(d[r+1]\)減去\((r-l+1)\)\(k\)表示該等差數列的結束.
  4. 而後對\(d[i]\)加上\(f[i]\)的前綴和,\(d[i]\)在加上自己的前綴和便可獲得最後真正的\(d[i]\).

\(f[i]\)的前綴和就是\(i\)位置最終的\(k\)值,再求一次前綴就是\(1\)\(i\)\(k\)之和,但因爲咱們只須要\([l+1,x]\)\(k\)值之和,因此還要將多出的減掉spa

不過咱們還有更簡單的解法code

法二:直接模擬:

注意到對於每一個p[i],使得\(p[i]==i\)的時間點只有一個,計算出這個時間點,若p[i]!=1,讓該時間內由p[i]>i變成p[i]==i的數++get

考慮每次右移操做形成的影響,能夠看作\(1\)\(n-1\)位置的數\(i+1\)\(n\)位置\(i=1\),若能維護出實時的大於\(0\)的個數和小於\(0\)的個數,再單獨考慮最後一個數,就能夠動態的計算答案。因爲使得\(p[i]==i\)的時間點只有一個,故容易預處理每一個時間的變化量。input

//細節太繁瑣,我崩潰了
//果真仍是太菜了 
#include<bits/stdc++.h>
using namespace std;

#define go(i,a,b) for(int i=a;i<=b;++i)
#define com(i,a,b) for(int i=a;i>=b;--i)
#define mem(a,b) memset(a,b,sizeof(a))
#define fo(i,a) for(int i=0;i<a;++i)
#define il inline

const int N=1e6+5;
typedef long long ll;

int p[N],cp=0,cn=0,mink,k,n;
ll sum,ans;
int idx[N];

il void read(int &x){
    x=0;char c=getchar(),f=1;
    while(!isdigit(c)){ if(c=='-') f=-1; c=getchar(); }
    while(isdigit(c)){ x=x*10+c-'0'; c=getchar(); }
    x*=f;
}

int main(){
    //freopen("input.txt","r",stdin);
    read(n);
    go(i,1,n) read(p[i]);
    go(i,1,n){
        idx[(p[i]-i+n)%n]++;
        if(p[i]>i)cp++;
        if(p[i]<=i)cn++;
        sum+=abs(p[i]-i);
    }
    mink=0;
    ans=sum;
    go(k,1,n-1){
        sum=sum+cn-1-cp;
        sum+=2*p[(n-k)%n+1]-1-n;
        cp++,cn--;
        cp-=idx[k],cn+=idx[k];
        if(sum<ans){
            mink=k;
            ans=sum;
        }
    }
    cout<<ans<<' '<<mink;
    return 0;
}
相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息