其他分享
首页 > 其他分享> > 【题解】做题记录(2022.9)

【题解】做题记录(2022.9)

作者:互联网

可能会断断续续的,是因为可能有的时候忘记了写记录

9.5

今天搞了一天的平衡树,但大部分都是比较基础的操作

[SHOI2009]会场预约

题目分析:

set 大法吼啊

我们考虑重新定义两个区间 \(A,B\) 的关系:

  1. =:\(A,B\)有交集
  2. <:\(A\) 完全在 \(B\) 的左边
  3. >:\(A\) 完全在 \(B\) 的右边

那么我们就使用 \(set\),在每次插入之前将有交集也就是等于的区间都删掉就好了。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
struct line{
	int l,r;
	line(){}
	line(int _l,int _r){
		l = _l,r = _r;
	}
};
bool operator < (line a,line b){return a.r < b.l;}
bool operator > (line a,line b){return a.l > b.r;}
bool operator == (line a,line b){return a.r >= b.l && a.l <= b.r;}
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	set<line> st;
	int q;
	scanf("%d",&q);
	for(int i=1; i<=q; i++){
		char opt;
		int l,r;
		cin>>opt;
		if(opt == 'A'){
			cin>>l>>r;
			int h = 0;
			set<line>::iterator it=st.find(line(l,r));
			while(it != st.end()){
				h++;st.erase(it);it=st.find(line(l,r));
			}
			st.insert(line(l,r));
			printf("%d\n",h);
		}
		else{
			printf("%d\n",st.size());
		}
	}
	return 0;
}

总结:
一定要灵活地运用 \(set\) 与平衡树之间的关系,对于这个题虽然平衡树可以写,但是显然 \(set\) 更好。

[HNOI2004]宠物收养场

题目分析:

如果当前数有,那么显然是当前数。否则最优的答案一定是当前数的前驱或者后继,然后就转化为了平衡树的基本操作。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e6+5;
const int INF = 1e17+5;
const int MOD = 1000000;
int cnt,rt,x,y,z,val[N],sz[N],key[N],ch[N][2];
int newnode(int x){
	++cnt;
	val[cnt] = x;sz[cnt] = 1;key[cnt] = rand();
	return cnt;
}
void pushup(int now){
	sz[now] = sz[ch[now][0]] + sz[ch[now][1]] + 1;
}
int merge(int x,int y){
	if(!x || !y)	return x + y;
	if(key[x] > key[y]){
		ch[x][1] = merge(ch[x][1],y);pushup(x);
		return x;
	}
	else{
		ch[y][0] = merge(x,ch[y][0]);pushup(y);
		return y;
	}
}
void split(int now,int v,int &x,int &y){
	if(!now){
		x = y = 0;return;
	}
	if(val[now] <= v){
		x = now;split(ch[now][1],v,ch[x][1],y);pushup(x);
	}
	else{
		y = now;split(ch[now][0],v,x,ch[y][0]);pushup(y);
	}
}
int find_rk(int now,int k){
	if(sz[ch[now][0]] >= k)	
		return find_rk(ch[now][0],k);
	else if(sz[ch[now][0]] + 1 == k)	
		return val[now];
	else	return 
		find_rk(ch[now][1],k - sz[ch[now][0]] - 1);
}
void insert(int v){
	split(rt,v,x,y);
	rt = merge(merge(x,newnode(v)),y);
}
void delet(int v){
	split(rt,v,x,y);
	split(x,v-1,x,z);
	rt = merge(x,y);
}
int find_pre(int v){
	split(rt,v-1,x,y);
	if(!sz[x])	return -INF;
	int ans = find_rk(x,sz[x]);
	rt = merge(x,y);
	return ans;
}
int find_suf(int v){
	split(rt,v,x,y);
	int ans = INF;
	if(sz[y])	ans = find_rk(y,1);
	rt = merge(x,y);
	return ans;
}
bool find(int v){
	split(rt,v,x,y);
	split(x,v-1,x,z);
	bool flag = false;
	if(sz[z])	flag = true;
	rt = merge(merge(x,z),y);
	return flag;
}
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	srand(time(NULL));
	int flag = 0;
	int q;
	scanf("%lld",&q);
	int ans = 0;
	while(q--){
		int opt,h;
		scanf("%lld%lld",&opt,&h);
		if(sz[rt] == 0){
			insert(h);
			flag = opt;
			continue;
		}
		if(opt == flag)	insert(h);
		else{
			if(find(h)){
				delet(h);
				continue;
			}
			int pre = find_pre(h);
			int suf = find_suf(h);
			if(abs(pre - h) <= abs(suf - h)){
				delet(pre);
				ans = (ans + abs(pre-h))%MOD;
			}
			else{
				delet(suf);
				ans = (ans + abs(suf - h))%MOD;
			}
		}
	}
	printf("%lld\n",ans);
	return 0;
}

[HNOI2002]营业额统计

题目分析:

与上个题类似,考虑这个式子其实就可以理解为这两个数在数轴上的距离,所以最优的答案一定是它前面的数中它的前驱或者后继,那么又转化为了平衡树的基本操作。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const long long MAXN = 4e4+5;
const long long INF = 1e18;
struct node{
	long long ch[2],fa,cnt,size,val;
	node(long long a1=0,long long a2=0,long long a3=0,long long a4=0,long long a5=0,long long a6=0){
		ch[0]=a1,ch[1]=a2,fa=a3,cnt=a4,size=a5,val=a6;
	}
};
long long n,root,tot,a[MAXN];
node tree[MAXN];
void update(long long x){
	tree[x].size = tree[tree[x].ch[1]].size + tree[tree[x].ch[0]].size + tree[x].cnt;
}
void rotate(long long x){
	long long y = tree[x].fa;
	long long z = tree[y].fa;
	long long k = tree[y].ch[1] == x;
	tree[z].ch[tree[z].ch[1] == y] = x;  tree[x].fa = z;
	tree[y].ch[k] = tree[x].ch[k^1];  tree[tree[x].ch[k^1]].fa = y;
	tree[x].ch[k^1] = y;  tree[y].fa = x;
	update(y);  update(x);
}
void Splay(long long x,long long goal){
	while(tree[x].fa != goal){
		long long y = tree[x].fa;
		long long z = tree[y].fa;
		if(z == goal){
			rotate(x);
			break;
		}
		if(z != goal){
			if((tree[z].ch[1] == y) == (tree[y].ch[1] == x))
				rotate(y);
			else
				rotate(x);
		}
		rotate(x);
	}
	if(goal == 0)
		root = x;
}
void find(long long x){  //找到值与 x 在平衡树上的位置 
	long long now = root;
	while(tree[now].val != x && tree[now].ch[x > tree[now].val]){
		now = tree[now].ch[x > tree[now].val];
	}
//	return now;
	Splay(now,0);
}
void Insert(long long x){  //插入数 x 
	long long now = root,fa=0;
	while(tree[now].val != x && now){
		fa=now;
		now = tree[now].ch[x > tree[now].val];
	}
	if(now){
		tree[now].cnt++;
	}
	else{
		now = ++tot;
		if(fa){
			tree[fa].ch[x > tree[fa].val] = now;
		}
		tree[now] = node(0,0,fa,1,1,x);
	}
	Splay(now,0);
}
long long Next(long long x,long long k){   //大于等于 or 小于等于,也就是在前驱和后继的前提下的最接近的数 
	find(x);
	long long now = root;
	if((tree[now].val == x) || (tree[now].val < x && !k) || (tree[now].val > x && k))
		return tree[now].val;
	now = tree[now].ch[k];
	while(tree[now].ch[k^1])	now = tree[now].ch[k^1];
	return tree[now].val;
}
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	Insert(INF);
	Insert(-INF);
	cin>>n;
	for(long long i=1; i<=n; i++)
		cin>>a[i];
	long long ans=0;
	for(long long i=1; i<=n; i++){  //保证找到的前驱和后继的编号都小于 i 也就是当前点的编号 
		long long pre = Next(a[i],0);
		long long nxt = Next(a[i],1);
		Insert(a[i]);
		if(pre == -INF && nxt == INF){
			ans += a[i];
		}
		else if(pre == -INF){
			ans += nxt - a[i];
		}
		else if(nxt == INF){
			ans += a[i] - pre;
		}
		else
			ans += min(a[i]-pre,nxt-a[i]);
	}
	printf("%lld\n",ans);
	return 0;
}

[TJOI2007]书架

题目分析:

对于维护区间的 fhq_treap 我们合并的顺序其实就是我们维护的区间上的顺序,所以分裂后按照题目的顺序合并就好了。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
string val[N];
int rt,cnt,x,y,z,sz[N],ch[N][2],key[N];
void pushup(int now){
	sz[now] = sz[ch[now][0]] + sz[ch[now][1]] + 1;
}
int merge(int x,int y){
	if(!x || !y)	return x + y;
	if(key[x] > key[y]){
		ch[x][1] = merge(ch[x][1],y);
		pushup(x);
		return x;
	}
	else{
		ch[y][0] = merge(x,ch[y][0]);
		pushup(y);
		return y;
	}
}
void split(int now,int k,int &x,int &y){
	if(!now){
		x = y = 0;
		return;
	}
	if(sz[ch[now][0]] < k){
		x = now;
		split(ch[now][1],k - sz[ch[now][0]] - 1,ch[x][1],y);
		pushup(x);
	}
	else{
		y = now;
		split(ch[now][0],k,x,ch[y][0]);
		pushup(y);
	}
}
int newnode(string v){
	++cnt;
	val[cnt] = v;sz[cnt] = 1;key[cnt] = rand();
	return cnt;
}
void get_out(int g){
	split(rt,g,x,y);
	split(x,g-1,x,z);
	cout<<val[z]<<endl;
	rt = merge(merge(x,z),y);
}
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int n;
	scanf("%d",&n);
	for(int i=1; i<=n; i++){
		string h;
		cin>>h;
		split(rt,i-1,x,y);
		rt = merge(merge(x,newnode(h)),y);
	}
	int q;
	scanf("%d",&q);
	for(int i=1; i<=q; i++){
		string h;
		int g;
		cin>>h>>g;
		split(rt,g,x,y);
		rt = merge(merge(x,newnode(h)),y);
	}
	scanf("%d",&q);
	for(int i=1; i<=q; i++){
		int g;
		scanf("%d",&g);g++;
		get_out(g);
	}
	return 0;
}

总结:
关键在于对于 \(merge\) 操作的理解,非常关键的一点就是对于维护区间的 fhq_treap,合并顺序即区间上的顺序。
对于这类操作显然 \(Splay\) 就不如 fhq_treap 好做好理解。

[HAOI2008]排名系统

题目分析:

这题也是平衡树的基本操作。
就是注意几点:

  1. 上传新记录的时候原有记录被覆盖,即需要删除
  2. 每个人维护一个时间戳,当得分一样时按时间戳排名
  3. 得分越高排名越小,得分相同时间戳越小排名越小

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int N = 3e5+5; 
struct node{
	int val,tag;
	string name;
	node(){}
	node(string _name,int _val,int _tag){
		name = _name,val = _val,tag = _tag;
	}
}val[N];
int cnt,rt,x,y,z,sz[N],key[N],ch[N][2];
map<string,node> mp;
bool operator < (node a,node b){   //注意:如果 val 相等认为越早越靠前 
	if(a.val == b.val)	return a.tag > b.tag;
	return a.val < b.val;
}
bool operator == (node a,node b){
	return a.val == b.val && a.tag == b.tag;
}
int newnode(node h){
	++cnt;
	val[cnt] = h;sz[cnt] = 1;key[cnt] = rand();
	return cnt;
}
void pushup(int now){
	sz[now] = sz[ch[now][0]] + sz[ch[now][1]] + 1;
}
int merge(int x,int y){
	if(!x || !y)	return x + y;
	if(key[x] > key[y]){
		ch[x][1] = merge(ch[x][1],y);
		pushup(x);
		return x;
	}
	else{
		ch[y][0] = merge(x,ch[y][0]);
		pushup(y);
		return y;
	}
}
void split(int now,node k,int &x,int &y){
	if(!now){
		x = y = 0;
		return;
	}
	if(val[now] < k || val[now] == k){
		x = now;
		split(ch[now][1],k,ch[x][1],y);
		pushup(x);
	}
	else{
		y = now;
		split(ch[now][0],k,x,ch[y][0]);
		pushup(y);
	}
}
string find_rk(int now,int k){  //求第 k 大的 
	if(sz[ch[now][1]] >= k)	return find_rk(ch[now][1],k);
	else if(sz[ch[now][1]] + 1 == k)	return val[now].name;
	else	return find_rk(ch[now][0],k - sz[ch[now][1]] - 1);
}
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	srand(time(NULL));
	int n;
	scanf("%lld",&n);
	for(int i=1; i<=n; i++){
		char opt;
		cin>>opt;
		if(opt == '+'){
			string name;
			int val;
			cin>>name>>val;
			if(mp.count(name)){
				split(rt,mp[name],x,y);
				split(x,node(mp[name].name,mp[name].val,mp[name].tag + 1),x,z);
				//这个值就是小于 mp[name] 的最大的值 
				rt = merge(x,y);
			}
			node h = node(name,val,i);mp[name] = h;
			split(rt,h,x,y);
			rt = merge(merge(x,newnode(h)),y);
		}
		else{
			string c;
			cin>>c;
			if(c[0] >= 'A' && c[0] <= 'Z'){
				node h = mp[c];
				split(rt,h,x,y);
				printf("%lld\n",sz[y]+1);
				rt = merge(x,y);
			}
			else{
				int val = 0;
				for(int j=0; j<c.size(); j++){
					val = val * 10 + c[j] - '0';
				}
				for(int j=val; j<val + 10 && j <= sz[rt]; j++){
					cout<<find_rk(rt,j)<<' ';
				}
				cout<<endl;
			}
		}
	}
	return 0;
}

[JLOI2011]不等式组

题目分析:

这个题显然可以将操作一转换为 \(x > C\) 或 \(x < C\),那么就可以转化为平衡树上的基本操作了。
为了方便可以建两个平衡树,分别维护 \(a>0\) 和 \(a<0\) 的情况。
注意因为精度问题,所以显然不能直接用浮点数,可以考虑上取整或者下取整再通过加一减一来调整。
注意:\(a\) 可能等于 \(0\),此时就需要特判。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
struct Tree{
	int cnt = 0;
	int rt,x,y,z,val[N],sz[N],key[N],ch[N][2];
	void pushup(int now){
		sz[now] = sz[ch[now][0]] + sz[ch[now][1]] + 1;
	}
	int newnode(int v){
		++cnt;
		val[cnt] = v;sz[cnt] = 1;key[cnt] = rand();
		return cnt;
	}
	int merge(int x,int y){
		if(!x || !y)	return x + y;
		if(key[x] > key[y]){
			ch[x][1] = merge(ch[x][1],y);
			pushup(x);
			return x;
		}
		else{
			ch[y][0] = merge(x,ch[y][0]);
			pushup(y);
			return y;
		}
	}
	void split(int now,int k,int &x,int &y){
		if(!now){
			x = y = 0;
			return;
		}
		if(val[now] <= k){
			x = now;
			split(ch[now][1],k,ch[x][1],y);
			pushup(x);
		}
		else{
			y = now;
			split(ch[now][0],k,x,ch[y][0]);
			pushup(y);
		}
	}
	void insert(int v){
		split(rt,v,x,y);
		rt = merge(merge(x,newnode(v)),y);
	}
	void delet(int v){
		split(rt,v,x,y);
		split(x,v-1,x,z);
		z = merge(ch[z][0],ch[z][1]);
		rt = merge(merge(x,z),y);
	}
	int query_mx(int v){
		split(rt,v-1,x,y);
		int ans = sz[y];
		rt = merge(x,y);
		return ans;
	}
	int query_mn(int v){
		split(rt,v,x,y);
		int ans = sz[x];
		rt = merge(x,y);
		return ans;
	}
}t1,t2;
int opt1[N],tmp1[N],X[N],Y[N],Z[N];
bool flag[N];
char opt[N];
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int n;
	scanf("%d",&n);
	int ans = 0;
	int tot = 0;
	for(int i=1; i<=n; i++){
		scanf("%s",opt+1);
		if(opt[1] == 'A'){
			++tot;
			flag[tot] = true;
			int a,b,c;
			scanf("%d%d%d",&a,&b,&c);
			X[tot] = a;Y[tot] = b;Z[tot] = c;
			if(a == 0)	ans += b > c;
			else if(a < 0){
				tmp1[tot] = ceil((c-b) / (a * 1.0)) - 1;
				opt1[tot] = 1;
				t1.insert(tmp1[tot]);
			}
			else{
				tmp1[tot] = floor((c-b) / (a * 1.0)) + 1;
				opt1[tot] = 0;
				t2.insert(tmp1[tot]);
			}
		}
		else if(opt[1] == 'D'){
			int a;
			scanf("%d",&a);
			if(flag[a]){
				if(X[a] == 0 && Y[a] > Z[a])	ans--;
				if(opt1[a] == 1)	t1.delet(tmp1[a]);
				if(opt1[a] == 0)	t2.delet(tmp1[a]);
				flag[a] = false;
			}
		}
		else{
			int k;
			scanf("%d",&k);
			printf("%d\n",ans + t1.query_mx(k) + t2.query_mn(k));
		}
	}
	return 0;
}

*[TJOI2013]最长上升子序列

题目分析:

对于最长上升子序列,显然需要考虑 \(dp\) 做法。
即设 \(dp[i]\) 表示以 \(i\) 为结尾的最长上升子序列的长度。
转移即:

\[dp[i] = \max_{j=1\and x_j < x_i}^{i-1} dp[j]+1 \]

我们考虑新加入一个数会造成什么影响,注意:我们加入数的顺序是 \(1\) 到 \(n\)
假设在 \(x\) 位置加入,因为它比所有数都大所以不会对后面的数的 \(dp\) 值产生影响。
因为我们的 \(dp[i]\) 代表的是以 \(i\) 结尾,那么它也不会对前面的 \(dp\) 值产生影响。
所以唯一会变化的也就是当前数也就是新出现的 \(dp\) 值,显然 \(dp[x] = \max_{j=1}^{x-1} dp[j] + 1\),因为它比所有的数都大。
那么这也就是一个区间求最大值和单点插入的操作,使用平衡树就可以解决。
注意插入的位置对应的 \(merge\) 的顺序。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6+5;
int rt,x,y,z,cnt,dp[N],val[N],ch[N][2],sz[N],key[N],pos[N];
void pushup(int now){
	val[now] = max(dp[pos[now]],max(val[ch[now][0]],val[ch[now][1]]));
	sz[now] = sz[ch[now][0]] + sz[ch[now][1]] + 1;
}
int merge(int x,int y){
	if(!x || !y)	return x | y;
	if(key[x] < key[y]){
		ch[x][1] = merge(ch[x][1],y);
		pushup(x);
		return x;
	}
	else{
		ch[y][0] = merge(x,ch[y][0]);
		pushup(y);
		return y;
	}
}
void split(int now,int k,int &x,int &y){  //分裂排名也就相当于在维护区间 
	if(!now){
		x = y = 0;
		return;
	} 
	if(sz[ch[now][0]] < k){
		x = now;
		split(ch[now][1],k - sz[ch[now][0]] - 1,ch[x][1],y);
	}
	else{
		y = now;
		split(ch[now][0],k,x,ch[y][0]);
	}
	pushup(now);
}
int newnode(int v,int i){
	++cnt;
	val[cnt] = v;sz[cnt] = 1;key[cnt] = rand();pos[cnt] = i;
	return cnt;
}
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int n;
	scanf("%d",&n);
	for(int i=1; i<=n; i++){
		int pos;
		scanf("%d",&pos);
		split(rt,pos,x,y);
		dp[i] = val[x] + 1;
//		printf("%d\n",dp[i]);
		rt = merge(merge(x,newnode(dp[i],i)),y);
		printf("%d\n",val[rt]);
	}
	return 0;
}

总结:
从最基础的套路出发,一点点的发现问题的性质,然后最后转化为一个可以做的问题

[TJOI2010]中位数

题目分析:

显然这个题可以使用对顶堆维护,但是我们也可以考虑使用平衡树。
考虑中位数也就是可以理解为一个区间第 \(k\) 大的问题,那么也就是平衡树的基础操作了。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6+5;
int cnt,x,y,z,rt,key[N],ch[N][2],sz[N],val[N];
char opt[100];
int newnode(int v){
	++cnt;
	key[cnt] = rand();sz[cnt] = 1;val[cnt] = v;
	return cnt;
}
void pushup(int now){
	sz[now] = sz[ch[now][0]] + sz[ch[now][1]] + 1;
}
int merge(int x,int y){
	if(!x || !y)	return x + y;
	if(key[x] > key[y]){
		ch[x][1] = merge(ch[x][1],y);
		pushup(x);
		return x;
	}
	else{
		ch[y][0] = merge(x,ch[y][0]);
		pushup(y);
		return y;
	}
}
void split(int now,int k,int &x,int &y){
	if(!now){x = y = 0;return;}
	if(val[now] <= k){
		x = now;
		split(ch[now][1],k,ch[x][1],y);
		pushup(x);
	}
	else{
		y = now;
		split(ch[now][0],k,x,ch[y][0]);
		pushup(y);
	}
}
void insert(int v){
	split(rt,v,x,y);
	rt = merge(merge(x,newnode(v)),y);
}
int query(int now,int k){
	if(sz[ch[now][0]] >= k)	return query(ch[now][0],k);
	else if(sz[ch[now][0]] + 1 == k)	return val[now];
	else	return query(ch[now][1],k - sz[ch[now][0]] - 1);
}
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int n;
	scanf("%d",&n);
	for(int i=1; i<=n; i++){
		int a;
		scanf("%d",&a);
		insert(a);
	}
	int q;
	scanf("%d",&q);
	while(q--){
		int x;
		scanf("%s",opt + 1);
		if(opt[1] == 'a'){
			n++;scanf("%d",&x);
			insert(x);
		}
		else if(opt[1] == 'm'){
			if(n % 2 == 0)	printf("%d\n",query(rt,n/2));  //求第 k 小 
			else	printf("%d\n",query(rt,(n/2) + 1));
		}
	}
	return 0;
}

[TJOI2014]电源插排

题目分析:

我这个题比较麻烦,使用了 \(set\) + \(map\) + fhq_treap

先考虑如果我们可以知道每一个数的插入位置,那么我们求 \([l,r]\) 中插头的个数,也就是有多少个数满足大于等于 \(l\) 且小于等于 \(r\),平衡树的基本操作。
我们就可以考虑使用两个 \(set\) 维护每一个数插入的位置以及每一个区间的左右端点。
每一次插入一个数也就是找到最长的且最靠右的区间,这个可以通过重新定义小于来实现,然后将这个区间分裂为两个区间,然后再维护。
每一次删除一个数也就是找到这个数的位置,然后删除原本以这个数为左右端点的两个区间,然后再将这个两个合并后放回去。

注意因为我们同学的编号很大,所以需要使用 \(map\) 离散化。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6+5;
struct node{
	int l,r;
	node(){}
	node(int _l,int _r){
		l = _l,r = _r;
	}
};
bool operator < (node a,node b){
	if(a.r - a.l == b.r - b.l)	return a.l > b.l;
	return a.r - a.l > b.r - b.l;
}
multiset<int> st1;
multiset<node> len;
int tag[N],tot,x,y,z,rt,cnt,val[N],key[N],sz[N],ch[N][2];
map<int,int> mp;
void pushup(int now){
	sz[now] = sz[ch[now][0]] + sz[ch[now][1]] + 1;
}
int newnode(int v){
	++cnt;
	sz[cnt] = 1;val[cnt] = v;key[cnt] = rand();
	return cnt;
}
int merge(int x,int y){
	if(!x || !y)	return x + y;
	if(key[x] > key[y]){
		ch[x][1] = merge(ch[x][1],y);
		pushup(x);
		return x;
	}
	else{
		ch[y][0] = merge(x,ch[y][0]);
		pushup(y);
		return y;
	}
}
void split(int now,int k,int &x,int &y){
	if(!now){
		x = y = 0;
		return;
	}
	if(val[now] <= k){
		x = now;
		split(ch[now][1],k,ch[x][1],y);
		pushup(x);
	}
	else{
		y = now;
		split(ch[now][0],k,x,ch[y][0]);
		pushup(y);
	}
}
void insert(int v){
	split(rt,v,x,y);
	rt = merge(merge(x,newnode(v)),y);
}
void delet(int v){
	split(rt,v,x,y);
	split(x,v-1,x,z);
	z = merge(ch[z][0],ch[z][1]);
	rt = merge(merge(x,z),y);
}
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int n,q;
	scanf("%lld%lld",&n,&q);
	len.insert(node(0,n+1));
	st1.insert(0);
	st1.insert(n+1);
	while(q--){
		int k;
		scanf("%lld",&k);
		if(k != 0){  //用 set 维护区间找到插的位置,用 fhq_treap 维护具体地插以及查询。 
			if(!mp.count(k)){
				mp[k] = ++tot;
			}   
			k = mp[k];
			if(tag[k]){   
				multiset<int>::iterator it;
				it = st1.find(tag[k]);
				--it;
				int pre = *it;it++;it++;
				int suf = *it;
				len.erase(len.find(node(pre,tag[k])));
//				printf("Erase %lld %lld\n",pre,tag[k]);
				len.erase(len.find(node(tag[k],suf)));
//				printf("Erase %lld %lld\n",tag[k],suf);
				len.insert(node(pre,suf));
//				printf("Insert %lld %lld\n",pre,suf);
				st1.erase(st1.find(tag[k]));
				delet(tag[k]);
				tag[k] = 0;
			}
			else{
				multiset<node>::iterator it;
				it = len.begin();
				node a = *it;
				tag[k] = (a.r + a.l)/2;
				if((a.r - a.l + 1) % 2 == 0)	tag[k]++;
				len.erase(len.find(node(a.l,a.r)));
//				printf("Erase %lld %lld\n",a.l,a.r);
				len.insert(node(a.l,tag[k]));
//				printf("Insert %lld %lld\n",a.l,tag[k]);
				len.insert(node(tag[k],a.r));
//				printf("Insert %lld %lld\n",tag[k],a.r);
				st1.insert(tag[k]);
				insert(tag[k]);
			}
		}
		else{
			int l,r;
			scanf("%lld%lld",&l,&r);
			split(rt,l-1,x,y);
			split(y,r,y,z);
			printf("%lld\n",sz[y]);
			rt = merge(x,merge(y,z));
		}
	}
	return 0;
}

总结:
发挥 \(set\) 与平衡树各自的容易实现的地方,然后结合起来使用。

[SHOI2013]发牌

题目分析:

我们的销牌其实就可以理解为将前缀的一个区间转移到它的后缀上去,根据 fhq_treap 的 \(merge\) 的性质,直接将前缀区间拿出来,然后反着合并这个前缀和剩下的区间就好了。
对于发牌其实就是找到区间上最左边的点,也就是一直跳左儿子就好了,或者也可以理解为排名为 \(1\) 的数,写一个找排名的函数也行。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
int cnt,rt,x,y,z,sz[N],val[N],key[N],ch[N][2];
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
int newnode(int v){
	++cnt;
	val[cnt] = v;key[cnt] = rand();sz[cnt] = 1;
	return cnt;
}
void pushup(int now){
	sz[now] = sz[ch[now][0]] + sz[ch[now][1]] + 1;
}
int merge(int x,int y){
	if(!x || !y)	return x + y;
	if(key[x] < key[y]){
		ch[x][1] = merge(ch[x][1],y);
		pushup(x);
		return x;
	}
	else{
		ch[y][0] = merge(x,ch[y][0]);
		pushup(y);
		return y;
	}
}
void split(int now,int k,int &x,int &y){
	if(!now){
		x = y = 0;
		return;
	}
	if(sz[ch[now][0]] < k){
		x = now;
		split(ch[now][1],k - sz[ch[now][0]] - 1,ch[x][1],y);
		pushup(x);
	}
	else{
		y = now;
		split(ch[now][0],k,x,ch[y][0]);
		pushup(y);
	}
}
int find_rk(int now,int k){
	if(sz[ch[now][0]] >= k)	return find_rk(ch[now][0],k);
	else if(sz[ch[now][0]] + 1 == k)	return val[now];
	else	return find_rk(ch[now][1],k - sz[ch[now][0]] - 1);
}
int build(int l,int r){
	if(l > r)	return 0;
	int mid = (l + r)>>1;
	int now = newnode(mid);
	if(l == r){return now;}
	ch[now][0] = build(l,mid-1);
	ch[now][1] = build(mid+1,r);
	return now;
}
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int n;
	n=read();
	for(int i=1; i<=n; i++){rt = merge(rt,newnode(i));}
	int len = n;
	for(int i=1; i<=n; i++){
		int r;
		r=read();
		r %= len;
		split(rt,r,x,y);
		rt = merge(y,x);
		int now = rt;
		while(ch[now][0])	now = ch[now][0];
		printf("%d\n",val[now]);
		split(rt,1,x,y);
		rt = y;
		len--;
	}
	return 0;
}

标签:ch,return,val,记录,int,题解,long,2022.9,now
来源: https://www.cnblogs.com/linyihdfj/p/16659473.html