POJ 3368 Frequent values 线段树与RMQ解法

题意:给出n个数的非递减序列,进行q次查询。每次查询给出两个数a,b,求出第a个数到第b个数之间数字的最大频数。

如序列:-1 -1 1 1 1 1 2 2 3

第2个数到第5个数之间出现次数最多的是数字1,它的频数3。

思路:假设查询时的参数为a, b。这道题查询时有以下两种情况:

1、 num[a] = num[b]. 即区间内的数字全相同,此时答案为b - a + 1。

2、 如果不相同,则以一般情况来讨论。见下图。

 

因为序列为非递减序列,因此值相同的数字必然连续出现。将区间分为3部分。num[a]以及与它值相同的区域构成第一部分,num[b]以及与它值相同的区域构成第三部分。区间[a, b]中剩下的构成第二部分。

定义left[i]表示与num[i]值相等的数字从左起开始的下标,right[i]表示与num[i]值相等的数字从右起开始的下标。

由图易知,第二部分里的数字,left与right值均在区间[a,b]内。

当给出区间范围a,b后,第一部分在区间内出现的次数为right[a] - a + 1。第三部分在区间内出现的次数为b - left[b] + 1。

如果right[a] + 1 > left[b] - 1,说明区间没有第二部分,直接输出上面两个值中的较大者。

如果存在第二部分,需要求出第二部分里的最大频数。不过这次就非常好求了,因为所有的数开始和结束都是在第二部分中,不存在部分出现的情况。定义tmax[i] = right[i] - left[i] + 1。则第二部分里数字的最大出现次数,即为该区间内tmax的最大值。将该值求出后与前面一三部分求出的较大者比较,最大的值即为最终答案。

因为查询量巨大,当第二部分需要计算时,可以采用线段树或者rmq。

现将两种方法的代码都给出。根据提交的结果来看,线段树所需空间远小于rmq,且速度稍快一点(不排除服务器的偶然性以及我rmq代码的效率比较低等原因)。

线段树求解代码

 1 #include<stdio.h>
 2 #include<algorithm>
 3 #define lson l, m, rt << 1
 4 #define rson m + 1, r, rt << 1 | 1
 5 #define maxn 100020
 6 #define inf 0x3f3f3f3f
 7 using namespace std;
 8 
 9 int num[maxn], left[maxn], right[maxn], tmax[maxn<<2];
10 void PushUp(int rt)
11 {
12     tmax[rt] = max(tmax[rt<<1], tmax[rt<<1|1]);
13 }
14 void build(int l,int r,int rt)
15 {
16     if (l == r)
17     {
18         tmax[rt] = right[l] - left[l] + 1;
19         return;
20     }
21     int m = (l + r) >> 1;
22     build(lson);
23     build(rson);
24     PushUp(rt);
25 }
26 int query(int L,int R,int l,int r,int rt)
27 {
28     if (L <= l && r <= R) return tmax[rt];
29     int m = (l + r) >> 1;
30     int ret = -inf;
31     if (L <= m) ret = max(ret, query(L, R, lson));
32     if (m < R) ret = max(ret, query(L, R, rson));
33     return ret;
34 }
35 int main()
36 {
37     int n, q;
38     //freopen("data.in", "r", stdin);
39     while (~scanf("%d",&n) && n)
40     {
41         scanf("%d",&q);
42         for (int i = 0; i < n; i++)
43         {
44             scanf("%d",&num[i]);
45             if (!i || num[i] != num[i-1]) left[i] = i;
46             else left[i] = left[i-1];
47         }
48         for (int i = n - 1; i > -1; i--)
49         {
50             if (i == (n - 1) ||num[i] != num[i+1])
51                 right[i] = i;
52             else right[i] = right[i+1];
53         }
54         build(0, n - 1, 1);
55         while (q--)
56         {
57             int a, b;
58             scanf("%d%d",&a,&b);
59             a--; b--;
60             if (num[b] == num[a]) printf("%d\n", b - a + 1);
61             else
62             {
63                 int tem = max(right[a] - a + 1, b - left[b] + 1);
64                 if (right[a] + 1 > left[b] - 1) printf("%d\n",tem);
65                 else printf("%d\n", max(tem, query(right[a] + 1, left[b] - 1, 0, n - 1, 1)));
66             }
67         }
68     }
69     return 0;
70 }
View Code

================================

rmq st算法求解代码

 1 #include<stdio.h>
 2 #include<math.h>
 3 #include<algorithm>
 4 #define maxn 100020
 5 using namespace std;
 6 
 7 int num[maxn], left[maxn], right[maxn], tmax[maxn][33];
 8 void st(int n)
 9 {
10     int k = (int)(log((double)n) / log(2.0));
11     for (int i = 0; i < n; i++)
12         tmax[i][0] = right[i] - left[i] + 1;//递推的初值
13     for (int j = 1; j <= k; j++)
14         for (int i = 0; i + (1 << j) - 1 < n; i++)
15         {
16             int m = i + (1 << (j - 1));//求出中间值
17             tmax[i][j] = max(tmax[i][j-1], tmax[m][j-1]);
18         }
19 }
20 //查询i和j之间的最值,注意i是从0开始的
21 int rmq(int i, int j)
22 {
23     int k = (int)(log(double(j - i + 1)) / log(2.0));
24     int t1 = max(tmax[i][k], tmax[j-(1<<k)+1][k]);
25     return t1;
26 }
27 int main()
28 {
29     int n, q;
30     //freopen("data.in", "r", stdin);
31     while (~scanf("%d",&n) && n)
32     {
33         scanf("%d",&q);
34         for (int i = 0; i < n; i++)
35         {
36             scanf("%d",&num[i]);
37             if (!i || num[i] != num[i-1]) left[i] = i;
38             else left[i] = left[i-1];
39         }
40         for (int i = n - 1; i > -1; i--)
41         {
42             if (i == (n - 1) ||num[i] != num[i+1])
43                 right[i] = i;
44             else right[i] = right[i+1];
45         }
46         st(n);
47         while (q--)
48         {
49             int a, b;
50             scanf("%d%d",&a,&b);
51             a--; b--;
52             if (num[b] == num[a]) printf("%d\n", b - a + 1);
53             else
54             {
55                 int tem = max(right[a] - a + 1, b - left[b] + 1);
56                 if (right[a] + 1 > left[b] - 1) printf("%d\n",tem);
57                 else printf("%d\n", max(tem, rmq(right[a] + 1, left[b] - 1)));
58             }
59         }
60     }
61     return 0;
62 }
View Code

 

转载于:https://www.cnblogs.com/fenshen371/p/3223040.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值