平衡树.st表.离散化练习总结

AT_abc350_f [ABC350F] Transpose

1.本题是平衡树,但可以用dfs解决:维护一个dfs,其中dfs(l,r,f)表示模拟的区间[l,r]是正序还是倒序,但是可能会遇到括号,所以要反向模拟,预处理出每个左括号对应的右括号,以O(1)的复杂度模拟。

2.关于大小写的转化,由手搓的样例可知:如果正序输出,大小写不变,反之则互换,思路也就清晰明了了

代码呈上:

#include<bits/stdc++.h>
using namespace std;
string s;
int n,ld[500005],rd[500005];
stack<int>st;
void dfs(int l,int r,int f) {
	if(f==1) {
		for(int i=l; i<=r; i++) {
			if(s[i]=='('){
				dfs(i+1,ld[i]-1,-1);
				i=ld[i];
			} 
			else{
				cout<<s[i];
			}
		}
	}
	else{
		for(int i=r;i>=l;i--){
			if(s[i]==')'){
				dfs(rd[i]+1,i-1,1);
				i=rd[i];
			} 
			else{
				if('a'<=s[i]&&s[i]<='z')cout<<char(s[i]-'a'+'A');
				else cout<<char(s[i]-'A'+'a');
			}
		}
	}
}
int main() {
	cin>>s;
	n=s.length();
	s=' '+s;
	for(int i=1; i<=n; i++) {
		if(s[i]=='(') {
			st.push(i);
		}
		if(s[i]==')') {
			int v=st.top();
			st.pop();
			ld[v]=i;
			rd[i]=v;
		}
	}
	dfs(1,n,1);
	return 0;
}

P10798 「CZOI-R1」消除威胁

1.数据全部绝对值化对本题没有影响,再进行离散化处理,值域就变成1~5e5

2.把每一种数单独拿出来,设有一个正整数序列,满足Ak1=Ak2=...=Akm=x,1=k1<k2<...<km=n,并且所有数都小于x,如果让以x为左右端点的威胁区间尽量少,设将x个设为负数,y个设为正数,则威胁区间有(x+y)/2个,当x等于0时,该操作无法进行,因为0=-0,直接算总数即可,对原序列从前往后遍历,记录每一个数上次出现的位置,用st表计算区间最小值,判断该区间是否是威胁区间,若是,则将区间内该数的个数加1,否则计算答案并清空。

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=500860;
unordered_map<int,vector<int> > m;
unordered_map<int,int> l;
int n,t,a[N],s[N][20];
LL ans,f[N],f2[N];
int qy(int l,int r){
	int t=log2(r-l+1);
	return max(s[l][t],s[r-(1<<t)+1][t]);
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		scanf("%d",a+i);
		a[i]=abs(a[i]);
		m[a[i]].push_back(i);
	}
	for(int i=3;i<=n;i++){
		if(i&1) t++;
		f[i]=f[i-1]+t;
	}
	for(int i=2;i<=n;i++)
		f2[i]=f2[i-1]+i-1;
	for(int j=0;j<20;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
			if(!j) s[i][j]=a[i];
			else s[i][j]=max(s[i][j-1],s[i+(1<<j-1)][j-1]);
	for(int i=1;i<=n;i++){
		int cnt=0,j=l[a[i]];
		for(;j<m[a[i]].size();j++){
			if(qy(i,m[a[i]][j])>a[i]) break;
			else cnt++;
		}
		l[a[i]]=j;
		if(!a[i]) ans+=f2[cnt];
		else ans+=f[cnt];
	}
	cout<<ans;
	return 0;
}

CF911D Inversion Counting

1.题意是将一段区间翻转,我们会非常轻易地发现 原先是正序的翻转后变成了逆序的,并且翻转的部分只对该区间起作用 ,所以翻转后逆序对数量的奇偶性也就是翻转前正序对的数量的奇偶性,但他们两者的奇偶性是不好求的(其实也可以直接判断二者的奇偶性)

2.先设该给定区间的长度为lenth,则该区间内所有数对数量为lenth(lenth-1)/2,需要注意所有数对数量而不是什么逆序对数量,再设翻转后的逆序对数量为part 所以翻转前的正序对数量也为part,这个前面提到过了,不懂的往前找,所以翻转前的逆序对数量就为lenth(lenth-1)/2-part,然后,翻转前的逆序对数量减去翻转前后逆序对数量差就是翻转后逆序对的数量,所以如果要判断翻转后逆序对数量的奇偶性只需要判断反转前后逆序对数量差的奇偶性即可。

这个数量差可以表示为:lenth(lenth-1)/2-part-part,也就是:lenth(lenth-1)/2-part*2,其中part*2一定是偶数,所以直接判断lenth(lenth-1)/2即可。

#include<bits/stdc++.h>
using namespace std;
int n,m;
int lenth,part;
int l,r,f;
int a[1550];
int main(){
	while(~scanf("%d",&n)){
		for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
		scanf("%d",&m);
		lenth=0;
		for(int i=1;i<n;i++)
			for(int j=i+1;j<=n;j++)
				if(a[i]>a[j]) lenth++;
		if(lenth%2==0) f=1;
		else f=0;
		while(m--){
			scanf("%d%d",&l,&r);
			part=r-l+1;
			if((part*(part-1)/2)%2==1) f=!f;
			if(f) printf("even\n");
			else printf("odd\n");
		}
	}
	return 0;
}

CF140C New Year Snowmen

1.题意:给你n个数,从中尽可能多的选出三元组满足三个数都不相同,最后按照降序顺序输出这些三元组

2.

去重:因为题目要求三个数互不相同 , 所以我们不能把所有数独立的看待 (因为可能会选到重复的数),但注意到ai的范围是1e9,不能直接开桶存,于是用map哈希一下 ,把这是第几个不同的数作为哈希的值记得顺便统计该种元素出现的次数

贪心:策略是从所有数中选出三个出现次数最多的数,循环这个操作知道剩下的数少于3个,考虑到每次选完三个数之后会更改他们的出现次数,我们需要维护所有数的出现次数,于是用priority_queue来完成这项操作。

三元组排序:可以借鉴冒泡排序的思想,把最大的数放到最前面去。比如:

if(y<z)y^=z,z^=y,y^=z;
if(x<z)x^=z,z^=x,x^=z;
if(x<y)x^=y,y^=x,x^=y;

最后代码呈上:

#include<bits/stdc++.h>
using namespace std;
int n,m,ji,mem[100005][4];
int read(){
	int x=0;char ch=getchar();
	while(ch>'9'||ch<'0')ch=getchar();
	while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return x;
}
struct node{
	int s,cnt;
}a[1000005];
map<int,int>mp;
priority_queue<pair<int,int> >dui;
bool cmp(node aa,node bb){
    return aa.cnt>bb.cnt;
}
int main(){
	n=read();
	int ci;
	for(int i=1;i<=n;i++){
		ci=read();
		if(!mp[ci]) ++ji,mp[ci]=ji,a[ji].s=ci;
		++a[mp[ci]].cnt;
	}
	for(int i=1;i<=ji;i++) dui.push(make_pair(a[i].cnt,a[i].s));
	int ans=0;
	while(dui.size()>=3){
		int x=dui.top().second,cntx=dui.top().first;
		dui.pop();
		int y=dui.top().second,cnty=dui.top().first;
		dui.pop();
		int z=dui.top().second,cntz=dui.top().first;
		dui.pop();
		if(y<z)y^=z,z^=y,y^=z,cnty^=cntz,cntz^=cnty,cnty^=cntz;
		if(x<z)x^=z,z^=x,x^=z,cntx^=cntz,cntz^=cntx,cntx^=cntz;
		if(x<y)x^=y,y^=x,x^=y,cntx^=cnty,cnty^=cntx,cntx^=cnty;
		mem[++ans][1]=x,mem[ans][2]=y,mem[ans][3]=z;
		if(cntx>1) dui.push(make_pair(cntx-1,x));
		if(cnty>1) dui.push(make_pair(cnty-1,y));
		if(cntz>1) dui.push(make_pair(cntz-1,z));
	}
	printf("%d\n",ans);
	for(int i=1;i<=ans;i++){
		printf("%d %d %d\n",mem[i][1],mem[i][2],mem[i][3]);
	}
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值