區間翻轉 歸併排序 + 逆序對
阿新 • • 發佈:2020-09-07
這個題目如果不知道怎麼用歸併排序求逆序對還是很難想的,但是知道用歸併排序求逆序對也不是一個很好寫的題目。
注意題目是每一個數只出現一次!!!
思路:
整體求考慮每一次操作的影響。
因為每次操作都是2的冪次,那麼我們先求出劃分成\(2^x\) 塊的每一個塊內的逆序對之和,這個可以用歸併排序做,所以假設 \(dp[x]\) 表示每一個塊大小是 \(2^x\) 次方的所有塊逆序對之和(注意這個定義是大小是\(2^x\) 塊),那麼容易知道,一個塊的逆序對+順序對=\(len*(len-1)/2\) 這個 \(len\) 表示塊的大小。
接下來考慮如果要求劃分成 $2^q $ 塊,那麼每塊大小就是 \(2^x = 2^n - 2^q\)
最後得出來的式子就是:\(f[i]\) 表示 \(2^i\) \(temp[i]\) 表示這次操作未更新之前的 \(dp[i]\)
\(i<=x\) : \(dp[i] = f[i]*(f[i]-1)/2*f[n-i]-dp[i]\)
\(i>x\) :\(dp[i] = dp[i]-temp[i-1]+dp[i-1]\)
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 3e6+10; ll a[maxn],temp[maxn],dp[40],f[40]; ll merge(int pos,int l,int r){ if(pos==0){ dp[pos] = 0;return 0;} ll ans = 0; int mid = (l+r)>>1; ans += merge(pos-1,l,mid); ans += merge(pos-1,mid+1,r); int now = 0,x = l,y = mid + 1; while(x<=mid||y<=r){ if(x<=mid&&y<=r) { if(a[x]<=a[y]) temp[++now]=a[x],x++; else temp[++now]=a[y],y++,ans+=mid-x+1; } else if(x<=mid) temp[++now] = a[x],x++; else if(y<=r) temp[++now] = a[y],y++; } dp[pos] += ans; for(int i=l,j=1;i<=r&&j<=now;i++,j++) a[i] = temp[j]; return ans; } void init(){ f[0] = 1; for(int i=1;i<=22;i++) f[i]=f[i-1]*2; } int main(){ init(); int n,m; scanf("%d%d",&n,&m); int len = 1<<n; for(int i=1;i<=len;i++) scanf("%lld",&a[i]); merge(n,1,len); while(m--){ int x; scanf("%d",&x); x = n-x; for(int i=0;i<=n;i++) temp[i] = dp[i]; for(int i=0;i<=x;i++) dp[i] = f[n-i]*f[i]*(f[i]-1)/2-dp[i]; for(int i=x+1;i<=n;i++) dp[i] = dp[i] - temp[i-1] + dp[i-1]; printf("%lld\n", dp[n]); } return 0; }