• 洛谷 P4168 [Violet]蒲公英 题解


    洛谷 P4168 [Violet]蒲公英题解

    2019-12-25

    题意

    给定一个含有n个数(1<=n<=4e4)的序列,保证每个数都小于1e9,给定m个询问(1<=m<=5e4),每次询问给定一个区间[l,r],求区间众数,若有多个众数,则输出最小的一个。注意:题目有lastans,要求强制在线

    题解

    首先先将序列离散化(废话)。注意到n,m都只有不到1e5的规模,容易想到分块系的做法(n的3/2次或5/3次)。但是区间众数这个东西既不满足区间减、只满足区间加,而区间加又是暴力的O(n),因此不可能用传统分块进行维护。因此考虑到我们将每个块的左右端点两两进行匹配视为不同的区间(即每个块的左端点与所有其右侧的块的右端点都视为不同的区间),暴力的算出每个区间的每个数的出现情况和区间众数。每次找到最近的块进行扩展和查询,查询完毕后再还原即可。接下来要考虑的问题是块的大小。不妨设共有x个块。则预处理部分的复杂度是O(n×k2),询问部分的复杂度是O(nm/k)的。很明显,当预处理和查询的复杂度接近时,时间复杂度最小。由于n,m大小相仿,不妨设其复杂度统计,则解方程n×k2=nm/k,解得k=n1/3。所以每个块的大小为n2/3最合适,整个代码的复杂度大约是O(n^5/3),大约在7e7的样子,刚好卡线过。(其实常数蛮大的,最慢的要1.01s,要不是时限是2s就扑街了)

    Code

    #include<bits/stdc++.h>
    using namespace std;
    inline int read()//快读
    {
    	int ret=0,w=1;
    	char ch=getchar();
    	while(ch<'0'||ch>'9')
    	{
    		if(ch=='-') w=-1;
    		ch=getchar();
    	}
    	while(ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
    	return w*ret;
    }
    int n,m;
    int a[40010];//原数组
    int b[40010];//离散化用数组
    map<int,int> mp,mp2;//正向+反向离散化
    int t,tot;//t为块大小,tot为块的数目
    struct node{
    	int type[40010];
    	int maxn;
    }f[1510];//保存每个区间的信息
    int pos[40010];//所在块编号
    inline void prework()//预处理
    {
    	int cnt=0;
    	for(int i=1;i<=n;i++)
    	if(!mp[b[i]]) mp[b[i]]=++cnt,mp2[cnt]=b[i];
    	for(int i=1;i<=n;i++) a[i]=mp[a[i]];
    	t=pow(n,2.0/3.0);
    	tot=n/t;//计算块的大小和块的数量
    	if(tot*t<n) tot++;
    	for(int i=1;i<=tot;i++)//预处理所有的区间的每个数出现次数以及众数
    	for(int j=i;j<=tot;j++)
    	{
    		int lp=(i-1)*t+1,rp=min(j*t,n);//确定左右端点
    		int p=i*(tot+1)+j;//
    		for(int k=lp;k<=rp;k++)//暴力计算区间信息
    		{
    			f[p].type[a[k]]++;
    			if(f[p].type[a[k]]>f[p].type[f[p].maxn]||(f[p].type[a[k]]==f[p].type[f[p].maxn]&&f[p].maxn>a[k])) f[p].maxn=a[k];
    		}
    	}
    	for(int i=1;i<=n;i++) pos[i]=(i-1)/t+1;//计算pos数组
    }
    inline int solve(int l,int r)//查询
    {
    	int lp=pos[l],rp=pos[r];
    	int p,tmp=0;
    	if(lp==rp||lp+1==rp)//若所在块相同或相邻,则无完整的区间信息可以调用,直接一个个计算即可
    	{
    		p=0;
    		for(int i=l;i<=r;i++)
    		{
    			f[p].type[a[i]]++;
    			if(f[p].type[a[i]]>f[p].type[f[p].maxn]||(f[p].type[a[i]]==f[p].type[f[p].maxn]&&f[p].maxn>a[i])) f[p].maxn=a[i];
    		}
    		int ans=f[p].maxn;
    		for(int i=l;i<=r;i++) f[p].type[a[i]]--;
    		f[p].maxn=0;
    		return ans;
    	}
    	lp++,rp--;//否则lp和rp中间的部分是完整的块,找到合适的区间信息并以此为基础扩展
    	p=lp*(tot+1)+rp,tmp=f[p].maxn;
    	for(int i=l;i<=(lp-1)*t;i++)//统计块左部分
    	{
    		f[p].type[a[i]]++;
    		if(f[p].type[a[i]]>f[p].type[f[p].maxn]||(f[p].type[a[i]]==f[p].type[f[p].maxn]&&f[p].maxn>a[i])) f[p].maxn=a[i];
    	}
    	for(int i=rp*t+1;i<=r;i++)//统计块右部分
    	{
    		f[p].type[a[i]]++;
    		if(f[p].type[a[i]]>f[p].type[f[p].maxn]||(f[p].type[a[i]]==f[p].type[f[p].maxn]&&f[p].maxn>a[i])) f[p].maxn=a[i];
    	}
    	int ans=f[p].maxn;//计算答案
    	for(int i=l;i<=(lp-1)*t;i++) f[p].type[a[i]]--;//还原现场
    	for(int i=rp*t+1;i<=r;i++) f[p].type[a[i]]--;
    	f[p].maxn=tmp;
    	return ans;
    }
    int lastans=0;
    int main()
    {
    	n=read(),m=read();
    	for(int i=1;i<=n;i++) a[i]=read(),b[i]=a[i];
    	sort(b+1,b+n+1);
    	prework();
    	for(int i=1;i<=m;i++)
    	{
    		int l=read(),r=read();
    		l=(l+lastans-1)%n+1,r=(r+lastans-1)%n+1;//记得计算lastans
    		if(l>r) swap(l,r);
    		lastans=mp2[solve(l,r)];//此处要注意!!!lastans所保存的答案是原数组中的数而非离散化后的
    		printf("%d\n",lastans);
    	}
    	return 0;
    }
    

    后记

    这道题本来应该30min就A的,结果因为各种愚蠢的锅而跟傻子一样调了好久。最后就因为lastans存了离散化后的答案而差点自闭QAQ比较可惜的是这道题黑掉紫了,否则就做出了人生第一道黑体了啊……最后祝大家圣诞节快乐o(╥﹏╥)o
  • 相关阅读:
    ruby and watir学习之_正则表达式
    ruby and watir学习之_String 类
    ruby and watir学习之_Array 类
    ruby and watir学习之_Numeric类&&Float 类
    ruby and watir学习之_Hash 类
    solrj使用demo
    Apache Solr索引富文本(html word pdf)
    TPCC简单计算法
    Apache Solr solrconfig.xml 中文说明
    Apache Solr的索引和查询顺序
  • 原文地址:https://www.cnblogs.com/xiaoh105/p/12104542.html
Copyright © 2020-2023  润新知