题面

  1. 一个数组B,如果有其中一个元素出现的次数大于length(B) div 2,那么该元素就是数组B的主元素,显然数组B最多只有1个主元素,因为数组B有主元素,所以被称为“优美的”。
  2. 给出数组A[0..n-1],问数组A有多少个“优美的”子数组。数组A的子数组是由数组A的连续若干个元素构成的数组。数组A不是直接给出的,而是通过如下公式自动产生的:
  3. for i = 0 to n-1 do
  4. {
  5. A[i] = (seed div 2^16) % m
  6. seed = (seed * 1103515245 + 12345) % 2^31
  7. }
  8. 如上公式中: n, seed, m都是输入数据给出的,div是表示整数的整除。^是表示幂运算。
  1. 输入格式:
  2. 一行,3个整数,n, seed, m1 <= n <= 100000 0 <= seed <= 2^31-1 1 <= m <= 50
  1. 输出格式
  2. 一个整数。

样例

  1. 输入样例1
  2. 5 200 5
  3. 输出样例1
  4. 8
  5. 输入样例2
  6. 10 15 3
  7. 输出样例2
  8. 23
  9. 输入样例3
  10. 8 12345678 1
  11. 输出样例3
  12. 36
  13. 输入样例4
  14. 27 541 50
  15. 输出样例4
  16. 27

1s,256MB

思路

由题目可以发现主元素在每一个子数组里只有一个。并且m很小,这说明了生成出的A[i]最大值只有m-1那么大。

m最多只有50,所以我们可以枚举每一个元素作为主元素在A数组中出现了多少次,然后最后累加一下答案就ok了。

那么现在问题来的,如何计算一个元素为主元素在A数组用出现了多少次。

不妨设钦定的主元素是x

我们简单地做一个差分,把a[i]==x的位置都设为1,不然就设为-1,然后做出来一个差分数组。

不如举个例子:

  1. a[i]={0,0,1,2,0}
  2. 钦定主元素:x=0

那么做出的差分数组:

  1. 1 1 -1 -1 1
  2. b[i]={1,2,1,0,1}

那么这个差分数组是什么意思呢?不难发现,如果我们要查询区间[1,3]中的主元素是不是x。将b[3]-b[1-1]就得出了1,这个1的意思是区间[1,3]中(x的个数)与(不是x的个数)的差。明显,如果这个差大于0,就说明这个区间是主元素为x的区间。

那么我们知道这个差分数组的特性了,可以思考,我们枚举这个区间的开头i,那么可不可以快速算出有多少B数组的值与b[i-1]的大于0。

这时候就有了一个办法:我们维护一个可以支持查询kth的数据结构,然后每次直接区间查询[i+1,n]大于b[i-1]的数有多少。时间复杂度为O(nmlog(n)),而且特别难写。那么有没有什么办法可以转换成简单一点的呢?

第一层转换

前面说了,我们要查询大于b[i-1]的数有多少,那么我们可以把B数组的值域都记下来,为vis数组,那么上面样例的vis数组为

  1. vis[0]=1;
  2. vis[1]=3;
  3. vis[2]=1;

同时做一个后缀和 sum数组:

  1. sum[0]=5;
  2. sum[1]=4;
  3. sum[2]=1;

不难发现其实我们查询的大于b[i-1]的数有多少就是,sum[b[i-1]+1]。就不需要查询kth了。

但还有个问题,我们查询完sum数组,开头i往下一个跳,区间就少了一个b[i],vis[b[i]]要减1,自然,sum数组从开头到b[i]都要减1。

即当i=2时,vis数组应该是这样的

  1. vis[0]=1;
  2. vis[1]=2;
  3. vis[2]=1;

sum数组应该是这样的

  1. sum[0]=4;
  2. sum[1]=3;
  3. sum[2]=1;

这个问题很好解决,我们可以开一个树状数组来维护,每次查询(b[i-1]+1)的值,然后将1至b[i]都减1。

初始化树状数组tree[i]=sum[i]

时间复杂度:O(nmlog(n)),但代码好些了很多。下面会将第二层优化,时间复杂度将优化成O(nm),是在这个方法的基础上优化的,希望大家先理解这个方法。

代码实现的一些说明:

  1. 1、首先,我们写的时候要加上偏移值,因为vis数组下标有可能是负数
  2. 2、我实现中的zz的意思是一个指针,指向b[i-1]+1的值,因为我们发现每一次开头i的变化,b[i-1]到b[i]只有可能加1或减1,因为差分数组的特殊性。所以这个(b[i-1]+1)我就用了一个指针来维护。
  3. 例如:刚开始b[1-1]为0zz指向0a[1]为x,那么b[2-1]就会为1,那么zz就加1,指向1。如果a[2]不为x,那么b[3-1]就会为0,那么zz就减1,指向0

代码:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define int long long
  4. #define maxn 400001
  5. const int pyz=100501;
  6. int a[maxn],vis[maxn],b[maxn],sum[maxn],ans,tree[maxn];
  7. int n,SEED,m,Ans;
  8. inline int lowbit(int x)
  9. {return x&-x;}
  10. inline void generate(int n,int SEED,int m){ //生成A数组
  11. for(int i=1;i<=n;i++){
  12. a[i]=(SEED/65536ll)%m;
  13. SEED=(SEED*1103515245ll+12345ll)%2147483648ll;
  14. }
  15. }
  16. inline void add(int p,int val){
  17. while(p<maxn){
  18. tree[p]+=val;
  19. p+=lowbit(p);
  20. }
  21. }
  22. inline void add_(int x,int y,int sum){
  23. add(x,+sum);
  24. add(y+1,-sum);
  25. }
  26. inline int sum_(int p){
  27. ans=0;
  28. while(p!=0){
  29. ans+=tree[p];
  30. p-=lowbit(p);
  31. }
  32. return ans;
  33. }
  34. inline int solve(int x){
  35. b[0]=0;long long ans=0,zz=pyz,begin=0,t,sum=0; //记得要加上偏移值,负数数组存不了
  36. memset(tree,0,sizeof(tree));
  37. memset(vis,0,sizeof(vis));
  38. for(int i=1;i<=n;++i){
  39. if(a[i]==x)b[i]=b[i-1]+1; //计算b数组
  40. else b[i]=b[i-1]-1;
  41. vis[b[i]+pyz]++; //计算vis数组
  42. }
  43. for(int i=pyz*2;i>=1;--i){
  44. sum+=vis[i]; //计算sum数组,后缀和
  45. add_(i,i,sum); //同时更新树状数组tree数组
  46. }
  47. for(int i=1;i<=n;++i){
  48. t=sum_(zz+1); //大于所以要加1
  49. if(a[i]==x)zz++;else zz--; //更改指针的值
  50. ans+=t;add_(1,zz,-1); //更新值
  51. }
  52. return ans;
  53. }
  54. signed main(){
  55. freopen("2828.in","r",stdin);
  56. freopen("2828.out","w",stdout);
  57. scanf("%lld%lld%lld",&n,&SEED,&m);
  58. generate(n,SEED,m);
  59. for(int i=0;i<m;i++){
  60. Ans+=solve(i);
  61. }
  62. printf("%lld\n",Ans);
  63. return 0;
  64. }

脸黑,常数大,别人O(nmlog(n))都过了,就我只有73分,不过这也激发了我探究O(nm)复杂度的决心。

第二层优化 时间复杂度O(nm)

辣么,我们现在来讲终究算法,不仅好写,时间复杂度还优。

我们现在来分析树状数组的做法,我们发现每一次指针跳只会一个一个跳,所以实际上树状数组改的很多地方都没有用,可能根本不会查询那里,所以这就导致了时间上的浪费。

既然指针只会一个一个跳,那么我们可以不用树状数组来维护,我们打标记tag,跳到哪,更新到哪。

打tag的方式也很简单,修改自身值的同时,把tag传的下一个去,自身清0。

  1. if(tag[zz]>0)sum[zz]-=tag[zz],tag[zz-1]+=tag[zz],tag[zz]=0;

是不是简单到爆炸!!!

这样通过指针的特殊性,我们把那个log的时间复杂度给省掉了,时间复杂度降为O(nm)。

代码:

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define int long long
  4. #define maxn 200011
  5. const int pyz=100001;
  6. int a[maxn],vis[maxn],b[maxn],sum[maxn],ans,tree[maxn],tag[maxn];
  7. int n,SEED,m,Ans;
  8. inline void generate(int n,int SEED,int m){
  9. for(int i=1;i<=n;i++){
  10. a[i]=(SEED/65536ll)%m;
  11. SEED=(SEED*1103515245ll+12345ll)%2147483648ll;
  12. }
  13. }
  14. inline int solve(int x){
  15. b[0]=0;long long ans=0,zz=pyz;
  16. memset(vis,0,sizeof(vis));memset(tag,0,sizeof(tag));
  17. for(register int i=1;i<=n;++i){
  18. if(a[i]==x)b[i]=b[i-1]+1;
  19. else b[i]=b[i-1]-1;
  20. vis[b[i]+pyz]++; //求vis数组
  21. }
  22. for(register int i=pyz*2;i>=1;--i)sum[i]=sum[i+1]+vis[i]; //求sum数组
  23. for(register int i=1;i<=n;++i){
  24. if(tag[zz]>0)sum[zz]-=tag[zz],tag[zz-1]+=tag[zz],tag[zz]=0; //打tag
  25. ans+=sum[zz+1]; //计算答案
  26. if(a[i]==x)zz++;else zz--;
  27. sum[zz]--;tag[zz-1]++; //更新tag,自身值
  28. }
  29. return ans;
  30. }
  31. signed main(){
  32. freopen("2828.in","r",stdin);
  33. freopen("2828.out","w",stdout);
  34. scanf("%lld%lld%lld",&n,&SEED,&m);
  35. generate(n,SEED,m);
  36. for(int i=0;i<m;++i)Ans+=solve(i);
  37. printf("%lld\n",Ans);
  38. return 0;
  39. }

哈哈哈,O(nm)是不是很简单啊。

你以为这是极限了吗?

不,事情远远没有你想想那么简单

如果m不是50了怎么办,m是10^9怎么办,会超时哦

第三层扩展性优化,m很大也能做!

时间复杂度O(nsqrt(n))

咳咳,这里只是一个扩展性的做法,针对m很大的做法,当然到这题没有用,不过还是写一下。

首先,第一步,将a[i]离散化,基础步骤

然后,第二步,分类讨论,如果元素出现种数小于sqrt(n),那么就直接跑上面O(nm)的做法,时间复杂度O(nsqrt(n))

接着,第三步,如果种数大于sqrt(n),那么就把出现次数小于sqrt(n)的元素为主元素的数组个数找出来,其实就是对于区间长度为[1,2sqrt(n)]的有主元素的子数组都找出来。但是如果,某个区间的主元素是元素x,这个元素x的总出现次数大于sqrt(n),那么就不加,避免与下面的计算重复。

那么,第四步,剩下的出现次数大于sqrt(n)的元素个数肯定不超过sqrt(n)个,这个简单证明一下就可以了。然后对于这几个元素跑一遍上面说的O(nm),找子数组个数。

最后,第五步,将全部答案加起来。

嗯~,这就是O(n sqrt(n))的做法。

谢谢观赏

smoj2828子数组有主元素的更多相关文章

  1. 笔试算法题(06):最大连续子数组和 & 二叉树路径和值

    出题:预先输入一个整型数组,数组中有正数也有负数:数组中连续一个或者多个整数组成一个子数组,每个子数组有一个和:求所有子数组中和的最大值,要求时间复杂度O(n): 分析: 时间复杂度为线性表明只允许一 ...

  2. Java算法-求最大和的子数组序列

    问题:有一个连续数组,长度是确定的,它包含多个子数组,子数组中的内容必须是原数组内容中的一个连续片段,长度不唯一,子数组中每个元素相加的结果称为子数组的和,现要求找出和最大的一个子数组. 具体算法如下 ...

  3. Java实现 LeetCode 795 区间子数组个数 (暴力分析)

    795. 区间子数组个数 给定一个元素都是正整数的数组A ,正整数 L 以及 R (L <= R). 求连续.非空且其中最大元素满足大于等于L 小于等于R的子数组个数. 例如 : 输入: A = ...

  4. 给定一个double类型的数组arr,其中的元素可正可负可0,返回子数组累乘的最大乘积。例如arr=[-2.5,4,0,3,0.5,8,-1],子数组[3,0.5,8]累乘可以获得最大的乘积12,所以返回12。

    分析,是一个dp的题目, 设f[i]表示以i为结尾的最大值,g[i]表示以i结尾的最小值,那么 f[i+1] = max{f[i]*arr[i+1], g[i]*arr[i+1],arr[i+1]} ...

  5. [LeetCode] Maximum Average Subarray II 子数组的最大平均值之二

    Given an array consisting of n integers, find the contiguous subarray whose length is greater than o ...

  6. 连续子数组和的最大值plus

    package wodeshiyao; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStre ...

  7. [LeetCode] 907. Sum of Subarray Minimums 子数组最小值之和

    Given an array of integers A, find the sum of min(B), where B ranges over every (contiguous) subarra ...

  8. [LeetCode] 644. Maximum Average Subarray II 子数组的最大平均值之二

    Given an array consisting of n integers, find the contiguous subarray whose length is greater than o ...

  9. C#LeetCode刷题之#581-最短无序连续子数组( Shortest Unsorted Continuous Subarray)

    问题 给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序. 你找到的子数组应是最短的,请输出它的长度. 输入: [2, 6, 4, 8, 10, ...

随机推荐

  1. 基于SILVACO ATLAS的a-IGZO薄膜晶体管二维器件仿真(06)

    在知网看到了江南大学的硕士论文: 双有源层a-IGZO薄膜晶体管的特性仿真 IGZO/IZO双有源层薄膜晶体管特性的模拟研究 发现,我昨天的文章中参数的设置存在重大失误,如下材料定义语句中: mate ...

  2. POJ 2142 The Balance(exgcd)

    嗯... 题目链接:http://poj.org/problem?id=2142 AC代码: #include<cstdio> #include<iostream> using ...

  3. X86汇编指令集大全【转】

    [原文地址]https://blog.csdn.net/bjbz_cxy/article/details/79467688[原文地址] ---------- 一.数据传输指令 ------------ ...

  4. MS17_010漏洞攻击Windows7

    攻击主机系统:Kali Linux 2018 目标主机系统:Windows7 x64 1.攻击主机启动Metasploit: msfconsole 2.查找MS17_010漏洞相关的信息: searc ...

  5. pta谁先倒

    传送门 #include <stdio.h> int main() { int x,y;//酒量 scanf("%d%d",&x,&y); int n; ...

  6. Spring学习(九)

    JdbcTemplate需要的jar包 1.Spring核心必须依赖的库:commons-logging-1.1.1.jar2.Spring IoC部分核心库: spring-beans-4.3.9. ...

  7. EAP认证

    EAP信息交换: 上图中展示的是OTP(一次性密码)实现EAP交换过程,具体的EAP交换过程如下: 步骤1:请求方向认证方发送EAPOL-Start消息,通知对方已经做到了认证准备(注意:若会话由认证 ...

  8. Promise解决回调地狱(多层调用问题)

    Promise # Promise 是异步编程的一种解决方案:从语法上讲,promise是一个对象,从它可以获取异步操作的消息:从本意上讲,它是承诺,承诺它过一段时间会给你一个结果.promise有三 ...

  9. vue+element ui table组件封装,使用render渲染

    后台管理经常会用到表格,一开始封装了一个常用的功能性表格,点击这里: 后来由于需求增加,在表格中还会用到switch,select,input等多种组件,每次都要在html中增加<el-tabl ...

  10. echarts做飞线图

    先上图,要不感觉没有说服力: 飞线图应该是大屏中很常见的一种了,通常你可以很轻易的用datav做一个飞线图,而且datav做的大屏逼格真的很高,本身也是开源免费的项目,开箱即用,上手简单……行了回归正 ...