• 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),如果感兴趣可以看一下我的方法,但最优的解法还是要学习一下的

  • 相关阅读:
    CCF|学生排队|Java
    CCF|分蛋糕|Java
    CCF|公共钥匙盒|Java
    CCF|打酱油|Java
    CCF|游戏|Java
    CCF|最小差值|Java
    CCF|路径解析|Java
    CCF|碰撞的小球
    CCF|跳一跳
    Win10 1803更新UWP无法安装的解决办法|错误代码0x80073D0D
  • 原文地址:https://www.cnblogs.com/TheShadow/p/10794349.html
Copyright © 2020-2023  润新知