• P1494 [国家集训队]小Z的袜子 题解


    CSDN同步

    原题链接

    简要题意:

    给定一个长度为 (n) 的数组 (a_i)(T) 组询问求 ([l,r]) 区间 随机抽到两个相等的数的概率

    (a_i,n,T leq 5 imes 10^4),时间限制 (100 ext{ms}).

    首先,这个题当然可以用分块、线段树维护平方和,用奇怪的数据结构解决。

    但是我们智商不够,数据结构也凑不上,所以考虑暴力。

    暴力?

    基于暴力

    大力暴力的话,可以达到 (mathcal{O}(nT)) 的时间复杂度。其具体实现是,把 ([l,r]) 区间的每个数拿出来做成一个桶(哈希),然后对桶内的数进行统计答案。

    但是出题人要卡你实在简单。它只需要一直询问 ([1 , 5 imes 10^4]) 这个区间,你的复杂度会卡到满。

    那你说了,切,我用记忆化记录 ([l,r]) 的答案好了。

    出题人于是把数据改了改:

    (i) 组询问为 ([i,n]).

    你会发现记忆化不行了。暴力也不行了。数据结构也不行了。似乎凉了?

    大力转移

    但是你会发现,通过 ([l,r] ightarrow [l,r+1]) 只需要 (mathcal{O}(1)) 的统计即可。这样的话,你可以把 (l) 点的同一个询问往两侧各做一遍哈希,一个个扩展,可以在 (mathcal{O}(n)) 的时间解决对 (l) 的询问

    但是很显然,出题人给你个随机数据你就撑不住。因为,你把 (mathcal{O}(n^2)) 个状态都枚举一遍是不现实的,而对不同的 (l) 你又无从下手。

    这时,莫队就应运而生了。

    莫队仅仅是考虑以下四个 (mathcal{O}(1)) 的转移:

    [[l,r] ightarrow [l,r+1] ]

    [[l,r] ightarrow [l,r-1] ]

    [[l,r] ightarrow [l-1,r] ]

    [[l,r] ightarrow [l+1,r] ]

    莫队历史

    我们来聊一聊关于莫队的发明历史吧。

    众所周知 ( ext{CodeForces}) 是个非常好的网站,里面的题目质量高,网站也以独特的赛制而闻名。大名鼎鼎的 珂朵莉树(老司机树,( ext{ODT}) 就是从 ( ext{CF}) 的一道赛题中引申的。

    在这个高级的圈子里,( ext{CF}) 的高级人士已经发现了类似的算法,并小范围的流传开去。但是很可惜,没有人对它进行系统的总结,因此始终没有大范围传播。

    后来 莫涛是第一个对莫队算法进行详细归纳总结的人,当时 “莫涛队长” 简称 “莫队”,他只分析了 普通莫队算法(不带修改的) 的实现,复杂度等。

    再后来,( ext{Oiers})( ext{Acmers}) 对莫队进行了修改操作上的优化,把莫队的应用推向了顶峰。因此有了树上莫队,回滚莫队等等。

    如何实现

    诚然莫队的基础是一种暴力。我们把询问的区间按照 ([l,r])( ext{pair}) 双关键字排序,然后第一个区间用 (mathcal{O}(n)) 的时间暴力出来,后面的区间则考虑上述的四个转移。

    可以证明,该算法的最劣时间复杂度为 (mathcal{O}(n sqrt{n})),具体证明可以去看 ( ext{OI - wiki}) 普通莫队算法 中的证明。

    实际上我们只需要维护一个桶,支持插入和删除,这非常简单。

    时间复杂度:(mathcal{O}(n sqrt{n})).

    实际得分:(100pts).

    #pragma GCC optimize(2)
    #include<bits/stdc++.h>
    using namespace std;
    
    typedef long long ll;
    const int N=5e4+1;
    
    inline int read(){char ch=getchar(); int f=1; while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
    	int x=0; while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x*f;}
    
    inline void write(int x) {
    	if(x<0) {putchar('-');write(-x);return;}
    	if(x<10) {putchar(char(x%10+'0'));return;}
    	write(x/10);putchar(char(x%10+'0'));
    }
    
    int n,m,c[N],cnt[N],b;
    ll sum,ans1[N],ans2[N]; //sum 记录临时答案,ans1 和 ans2 记录答案的排序
    
    struct node {
    	int l,r,id; //id 是询问编号
    	inline bool operator < (const node &x) const {
    		if(l/b!=x.l/b) return l<x.l;
    		return (l/b)&1?r<x.r:r>x.r;
    	} //排序
    } a[N];
    
    inline void add(int x) {sum+=(cnt[x]++);} //插入
    inline void del(int x) {sum-=(--cnt[x]);} //删除
    inline ll gcd(ll n,ll m) {return m?gcd(m,n%m):n;}
    
    int main() {
    	n=read(),m=read(); b=sqrt(n);
    	for(int i=1;i<=n;i++) c[i]=read();
    	for(int i=1;i<=m;i++) a[i].l=read(),a[i].r=read(),a[i].id=i;
    	sort(a+1,a+m+1);
    	for(int i=1,l=1,r=0;i<=m;i++) {
    		if(a[i].l==a[i].r){
    			ans1[a[i].id]=0; ans2[a[i].id]=1;
    			continue;
    		} //特殊区间按照题目要求更新
    		while(l<a[i].l) del(c[l++]);
    		while(l>a[i].l) add(c[--l]);
    		while(r<a[i].r) add(c[++r]);
    		while(r>a[i].r) del(c[r--]); //暴力移动
    		ans1[a[i].id]=sum;
    		ans2[a[i].id]=ll(r-l+1)*(r-l)/2; //暴力更新
    	} for(int i=1;i<=m;i++) {
    		if(ans1[i]) {
    			ll x=gcd(ans1[i],ans2[i]);
    			ans1[i]/=x; ans2[i]/=x;
    		} else ans2[i]=1; //约分
    		printf("%lld/%lld
    ",ans1[i],ans2[i]);
    	}
    	return 0;
    }
    
    
    
  • 相关阅读:
    System Verilog 片断
    如何避免covergroup中出现错误
    一种FPGA图像处理算法的快速验证方式
    什么才是一个feature to be test?
    我的第一份vPlan衍变路线
    思想误区解答:请专注于DUT的功能(全部为菜鸟个人总结不保证正确)
    谈谈验证中的SystemVerilog和CPP//转载
    ResourceBundleViewResolver
    springmvc json数据返回前台,中文乱码
    将字符串中间的某段长度替换成固定的值
  • 原文地址:https://www.cnblogs.com/bifanwen/p/13196463.html
Copyright © 2020-2023  润新知