题解:P15595 [ICPC 2020 Jakarta R] Shopping Changes

· · 题解

P15595 [ICPC 2020 Jakarta R] Shopping Changes

题意:

m 次询问,第 i 次询问给出一个长 l_i 的序列,问将一个长为 n 的连续段插入序列的间隙中可以得到的最小的逆序对数。

思路:

首先求出连续段本身和序列本身所有的逆序对数,这些是无法避免会贡献到答案里的,离散化后用树状数组可以完成这一步。

考虑把连续段插入哪个位置所得的逆序对数最少,对每一个位置设 bg_i 表示 w_i 比连续段中多少个数要大,lw_i 表示 w_i 比连续段中多少个数要小,将连续段排序后可以二分解决。

### 细节: 1. 要开 long long。 2. 不能每次询问都 memset 树状数组,会超时。 3. 因为我们考虑的是位置与位置之间的空隙,所以 $pre_i$ 与 $suf_i$ 的下标含义要考虑清楚。 4. 树状数组下标不能为 $0$。 ### 代码: ```cpp #include<bits/stdc++.h> using namespace std; #define int long long int n,m,len,bas; const int N = 2e5+10,inf=1e18; int c[N],b[N*2],c2[N]; int l[N],bg[N],lw[N]; vector<int> w[N]; int t[2*N],pre[N],suf[N]; int lowbit(int x){return x&(-x);} void add(int p,int x){ for(int i=p;i;i-=lowbit(i)){ t[i]+=x; } } int ask(int p){ int res=0; for(int i=p;i<=len+1;i+=lowbit(i))res+=t[i]; return res; } signed main(){ ios::sync_with_stdio(0); cin.tie(0),cout.tie(0); cin>>n>>m; for(int i=1;i<=n;i++){ cin>>c[i];c2[i]=c[i]; b[++len]=c[i]; } sort(c2+1,c2+1+n); for(int i=1;i<=m;i++){ cin>>l[i]; for(int j=1;j<=l[i];j++){ int x; cin>>x; w[i].push_back(x); b[++len]=x; } } sort(b+1,b+1+len); len=unique(b+1,b+1+len)-b-1; for(int i=1;i<=n;i++){ int x=lower_bound(b+1,b+1+len,c[i])-b; bas+=ask(x+2); add(x+1,1); } memset(t,0,sizeof(t)); for(int i=1;i<=m;i++){ int num=0; for(int j=0;j<l[i];j++){ int v=w[i][j]; int x=lower_bound(b+1,b+1+len,v)-b; num+=ask(x+2);add(x+1,1); bg[j]=lower_bound(c2+1,c2+1+n,v)-c2-1; lw[j]=n-(upper_bound(c2+1,c2+1+n,v)-c2)+1; } pre[0]=0; for(int j=1;j<=l[i];j++){ pre[j]=pre[j-1]+bg[j-1]; } suf[l[i]]=0; for(int j=l[i]-1;j>=0;j--){ suf[j]=suf[j+1]+lw[j]; } int ans=inf; for(int j=0;j<=l[i];j++){ ans=min(ans,bas+num+pre[j]+suf[j]); } cout<<ans<<endl; for(int j=0;j<l[i];j++){ pre[j]=suf[j]=0; int x=lower_bound(b+1,b+1+len,w[i][j])-b; add(x+1,-1); } } return 0; } ```