Couleur(主席树+启发式分裂)

很久之前学的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();
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值