很久之前学的dsu on tree,没想到还有启发式分裂
Couleur
题意:给定一个序列,一次操作可让一个位置失效,给定操作的序列(通过异或隐藏,强制在线)求每次操作后没有失效位置的连续子区间的最大逆序队的数量。每次操作会分割序列,求被分割出来的序列的最大逆序对数量。
思路:(只想到了主席树,然后参考题解)
如果没有操作,只求原始数组的逆序对数量,通过普通线段树,维护当前位置前面有多少个元素大于当前元素就可获得。针对某一位置一次操作会使得其前面或后面的大于或小于的数失效(对于该位置的贡献减小),难以用普通线段树维护。
考虑逆序对的求法,维护当前位置前面(后面)有多少个元素大于(小于)当前元素。那么如果使一个大区间
[
l
,
r
]
[l,r]
[l,r]被位置
x
x
x分割成两个小区间
[
l
1
,
r
1
]
,
[
l
2
,
r
2
]
[l_1,r_1],[l_2,r_2]
[l1,r1],[l2,r2]。
设某一区间的逆序对为
f
l
,
r
f_{l,r}
fl,r
位置x在
[
l
1
,
r
1
]
[l_1,r_1]
[l1,r1]里面的有效数量(大于
a
x
a_x
ax的数量)A
位置x在
[
l
2
,
r
2
]
[l_2,r_2]
[l2,r2]里面的有效数量(小于
a
x
a_x
ax的数量)B
逆序对前一个在区间
[
l
1
,
r
1
]
[l_1,r_1]
[l1,r1]后一个在区间
[
l
2
,
r
2
]
[l_2,r_2]
[l2,r2]的数量 C
f
l
,
r
=
f
l
1
,
r
1
+
f
l
2
,
r
2
+
A
+
B
+
C
f_{l,r}=f_{l_1,r_1}+f_{l_2,r_2}+A+B+C
fl,r=fl1,r1+fl2,r2+A+B+C
在一次切割的过程中,已知量为
f
l
,
r
f_{l,r}
fl,r,只需要维护出
f
l
1
,
r
1
f_{l_1,r_1}
fl1,r1和
f
l
2
,
r
2
f_{l_2,r_2}
fl2,r2即可。
只要枚举一个区间就能得到
C
C
C,就可以知道另一个区间的数量。所以我们只要枚举一个区间即可,明显的可以去枚举较小的区间。那么为什么不会超时呢?
切割的操作是一直存在的,序列会被不断地分割成一个一个小区间。如果每次都折半的话,每个位置被枚举的次数不会超过
l
o
g
(
n
)
log(n)
log(n)次。
(合并是去记录重儿子的信息,枚举轻儿子,也是
l
o
g
log
log可惜不会证明)
通过主席树就可以维护。然后再通过map和multiset维护被分割的区间和现有的逆序对数量即可。
//It's better to have sex than to do questions
#include<bits/stdc++.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#define fi first
#define se second
#define ll long long
#define ull unsigned long long
#define ld long double
using namespace std;
const int N=1e5+5;
const ll md=1e9+7;
const ll inf=1e18;
const double eps=1e-9;
const double E=2.718281828;
//vector<vector<int>>f(n,vector<int>(m,0));
//cout<<fixed<<setprecision(6)
struct sgtree{
int val,l,r,ls,rs;
};
sgtree tr[N*20];
int cnt=0;
int rt[N],a[N],b[N],n;
void add(int pre,int &now,int l,int r,int v){
now=++cnt;
tr[now]=tr[pre];
if(l==r){
tr[now].val++;
return ;
}
int m=l+r>>1;
if(v<=m){
add(tr[pre].ls,tr[now].ls,l,m,v);
}else{
add(tr[pre].rs,tr[now].rs,m+1,r,v);
}
tr[now].val=tr[tr[now].ls].val+tr[tr[now].rs].val;
}
int query(int now,int l,int r,int pl,int pr){// pl,pr之间的个数(到达当前位置的前缀和)
if(pl>pr)return 0;
if(pl<=l&&r<=pr){
return tr[now].val;
}
int m=l+r>>1;
int res=0;
if(pl<=m){
res+=query(tr[now].ls,l,m,pl,pr);
}
if(pr>m){
res+=query(tr[now].rs,m+1,r,pl,pr);
}
return res;
}
std::map<int, ll> mp;
std::multiset<ll> ms;
void split(int l,int r,int x){//位置l,r已经失效
// cout<<l<<" "<<r<<" "<<x<<endl;
ll all=mp[l];
ms.erase(ms.find(all));
ll base=0;
base+=query(rt[x-1],1,n,a[x]+1,n)-query(rt[l],1,n,a[x]+1,n);//[l+1,x-1]
base+=query(rt[r-1],1,n,1,a[x]-1)-query(rt[x],1,n,1,a[x]-1);//位置x 的逆序对数量
if(x-l<r-x){//启发式分裂枚举小的区间
ll A=0,B=base;
for(int i=l+1;i<x;i++){
A+=query(rt[i-1],1,n,a[i]+1,n)-query(rt[l],1,n,a[i]+1,n);
B+=query(rt[r-1],1,n,1,a[i]-1)-query(rt[x],1,n,1,a[i]-1);
}
mp[l]=A,ms.insert(A);
mp[x]=all-A-B,ms.insert(mp[x]);
}else{
ll A=0,B=base;
for(int i=x+1;i<r;i++){
A+=query(rt[i-1],1,n,a[i]+1,n)-query(rt[x],1,n,a[i]+1,n);//[x+1,i]
B+=query(rt[x-1],1,n,a[i]+1,n)-query(rt[l],1,n,a[i]+1,n);//[l+1,x-1];
}
mp[x]=A,ms.insert(A);
mp[l]=all-A-B,ms.insert(mp[l]);
}
}
void solve(){
cin>>n;
cnt=0;
for(int i=1;i<=n;i++){
cin>>a[i];
add(rt[i-1],rt[i],1,n,a[i]);
}
// cout<<"add end"<<endl;
ll tmp=0;
for(int i=1;i<=n;i++){
tmp+=query(rt[i-1],1,n,a[i]+1,n);
}
mp.clear(),ms.clear();
mp[0]=tmp,ms.insert(tmp);
mp[n+1]=0,ms.insert(0);
for(int i=1;i<=n;i++){
if(i<n)cout<<tmp<<" ";
else{
cout<<tmp<<"\n";
cin>>b[i];
break;
}
ll x;
cin>>x;
x=x^tmp;
// if(x<1||x>n){
// cout<<"wrong answer"<<endl;
// return ;
// }
auto it=prev(mp.lower_bound(x));//不小于
split(it->first,next(it)->first,x);
tmp=*prev(ms.end());
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--){
solve();
}
}