天天看点

Luogu P4670 [BalticOI 2011 Day2]Plagiarism 题解

  • 我最近是不是数据结构学傻了啊。。。
  • 这道题看是1e5,所以复杂度为(O(nlogn))的是完全可以跑过去的,然后看题,要求的对于每个数满足要求的区间的长度之和,我们自然而然的就可以想到用FHQ-Treap来维护这个序列了。

- ps:不会FHQ的小伙伴们可以学习一下,这也是一个比较好用的数据结构(特别是打暴力)。

  • 然后事情就很好办了。先建一个一颗平衡树,然后每次查询的时候直接用我们可奈的split操作分出满足条件的区间,然后直接统计答案即可。
  • 但是有一个小小的问题,我们每次分出来的序列没有包含与这个相同的数,但它们也是要统计进去的,怎么办呢?

    map,你值得拥有!

  • 我们可以用map记录一下每个出现的次数,相同的数之间形成的匹配数为$ frac {n*(n-1)}{2} $,因为每个数只能用一次,所以算过了之后要归0;
  • 基本思路就是这样,还有一些处理上的小细节,我会在代码中指出。

AC CODE:

#include<bits/stdc++.h>
#define clean(a,i) memset(a,i,sizeof(a))
#define ll long long
#define inl inline
#define il inl void
#define it inl int
#define ill inl ll
#define re register
#define ri re int
#define rl re ll
#define lc(cur) ch[cur][0]
#define rc(cur) ch[cur][1]
using namespace std;
template<class T>il read(T &x)
{
	int f=1;char k=getchar();x=0;
	for(;k>'9'||k<'0';k=getchar()) if(k=='-') f=-1;
	for(;k>='0'&&k<='9';k=getchar()) x=(x<<3)+(x<<1)+k-'0';
	x*=f;
}
const int MAXN = 1e5+5;
// 下方为FHQ-Treap的基本操作
int n,a[MAXN],ch[MAXN][2],val[MAXN],size[MAXN],rd[MAXN],cnt;
il pushup(int cur){size[cur]=size[lc(cur)]+size[rc(cur)]+1;}
it new_node(int a){
	val[++cnt]=a,size[cnt]=1,rd[cnt]=rand();
	return cnt;
}
it merge(int x,int y){
	if(!x||!y) return x+y;
	if(rd[x]<rd[y]){
		rc(x)=merge(rc(x),y);
		pushup(x);return x;
	}
	else{
		lc(y)=merge(x,lc(y));
		pushup(y);return y;
	}
}
il split(int cur,int k,int &x,int &y){
	if(!cur) x=y=0;
	else{
		if(val[cur]<=k) x=cur,split(rc(x),k,rc(x),y);
		else y=cur,split(lc(y),k,x,lc(y));
		pushup(cur);
	}
}
int root,x,y,z;
il insert(int val){split(root,val,x,y),root=merge(merge(x,new_node(val)),y);}
//打板子时间到此为止,下面是具体的一些操作细节
map<int,int> mp;//用来储存每个数出现的个数
ll ans;//注意要开longlong
int main()
{
	read(n);
	for(ri i=1;i<=n;i++) read(a[i]),mp[a[i]]++,insert(a[i]);//读入数据,对每个数的出现进行储存,顺便将这个节点插入到平衡树里
	for(ri i=1;i<=n;i++){
		ri t=a[i]*9/10;
		if(a[i]%10==0) t-=1;//这里的两步要注意下,我就是在这里死了好几次。首先是要先乘再除,这样可以保证精度。然后是后面的if判断,通过打表(或者直接推)可以发现,只有10的倍数乘上0.9才是一个整数。因为我们要将小于它的数中满足条件的分成两部分,所以为正数的时候边界还要左移(不太懂的可以自己举几个栗子,或者自己感性理解一下(逃))
		split(root,a[i]-1,x,z),split(x,t,x,y);
		ans+=size[y],root=merge(merge(x,y),z);//这里的两部都是FHQ的正常操作,顺带着统计下答案
	}
	for(ri i=1;i<=n;i++){
		ri sz=mp[a[i]];mp[a[i]]=0;//把每一个还没有计算过的数拿出来统计答案,级的统计过后要归0
		if(!sz) continue;
		ans+=1LL*sz*(sz-1)/2;
	}
	printf("%lld",ans);
	return 0;
}
           

最后再温馨提示一下,蒟蒻的这种做法其实并不是最优的,这道题可以做到O(n),如果感兴趣可以看一下我的方法,但最优的解法还是要学习一下的