P6510

P6510 奶牛排队(RMQ)

P6510 奶牛排队(RMQ)

P6510 奶牛排队 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目描述

奶牛在熊大妈的带领下排成了一条直队。

显然,不同的奶牛身高不一定相同……

现在,奶牛们想知道,如果找出一些连续的奶牛,要求最左边的奶牛 A 是最矮的,最右边的 B是最高的,且 B高于 A 奶牛。中间如果存在奶牛,则身高不能和 A,B 奶牛相同。问这样的奶牛最多会有多少头?

从左到右给出奶牛的身高,请告诉它们符合条件的最多的奶牛数(答案可能是 0,2,但不会是 1)。

输入格式

第一行一个正整数 N,表示奶牛的头数。

接下来 N行,每行一个正整数,从上到下表示从左到右奶牛的身高 hi。

输出格式

一行一个整数,表示最多奶牛数。

题解

1(过不了,太垃了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//呜呜,最坏情况O(n*n)过不了,tle了一个点
#include <stdio.h>
int find(int a[], int n);
int a[100005] = {};
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++)
scanf("%d", &a[i]);
int ans = find(a, n);
if (ans == 1)
ans = 0;
printf("%d", ans);
return 0;
}
int find(int a[], int n)
{
int left, right, ans = 0;
left = right = 0;
for (int i = 1; i < n; i++)
{
if (a[i] > a[right]) //读取的下一数大于最右边的数
right = i;
else if (a[i] <= a[left]) //读取下一个数小于等于最左边的数
left = right = i;
else if (a[left] < a[i] && a[right] >= a[i]) //读取的下一个数在两者之间,较复杂
{
//后面存在比最右边大的数且不存在比最左边小的数则right等于改变为那个数
//否则重新从now开始
int flag = 0, now = i;
for (; i < n; i++)
{
if (a[i] > a[right])
{
right = i;
flag = 1;
break;
}
if (a[i] <= a[left])
break;
}
if (flag == 0)
left = right = now;
i = now;
}
ans = ans > (right - left + 1) ? ans : right - left + 1;
}
return ans;
}

2(RMQ)

区间最值查询,先做预处理求最值

1
max[i][j]是从i个数开始包括i在内1<<j个数的最大值,min同理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int themin(int x, int y)
{
if (x < y)
return x;
return y;
}
int themax(int x, int y)
{
if (x > y)
return x;
return y;
}
//max[i][0],min[i][0]就是数组中的a[i]的值,本质是动态规划
void rmq(int n)
{
int i, j;
for (j = 1; (1 << j) <= n; j++)
{
for (i = 0; i + (1 << j) - 1 < n; i++)//保证不越界
{
max[i][j] = themax(max[i][j - 1], max[i + 1 << (j - 1)][j - 1]);
min[i][j] = themin(min[i][j - 1], min[i + 1 << (j - 1)][j - 1]);
}
}
return;
}

rmq区间二分,找到当前区间的序列,继续二分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <math.h>
#include <stdio.h>
void find(int left, int right);
void rmq(int n);
int a[100005] = {};
int max[100005][20] = {};
int min[100005][20] = {}; //max min 里面存放最值的位置
int ans = 0;
int themin(int x, int y)
{
if (a[x] < a[y])
return x;
return y;
}
int themax(int x, int y)
{
if (a[x] >= a[y])
return x;
return y;
}
int minpos(int left, int right)
{
int k = log(right - left + 1) / log(2);//一定要除log(2),骂骂咧咧,搞半天错在这里
return themin(min[left][k], min[right + 1 - (1 << k)][k]);
}
int maxpos(int left, int right)
{
int k = log(right - left + 1) / log(2);
return themax(max[left][k], max[right + 1 - (1 << k)][k]);
}
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
max[i][0] = min[i][0] = i;
}
rmq(n);
find(0, n - 1);
if (ans == 1)
ans = 0;
printf("%d", ans);
return 0;
}
void rmq(int n)
{
int i, j;
for (j = 1; (1 << j) <= n; j++)
{
for (i = 0; i + (1 << j) - 1 < n; i++)
{
max[i][j] = themax(max[i][j - 1], max[i + (1 << (j - 1))][j - 1]);
min[i][j] = themin(min[i][j - 1], min[i + (1 << (j - 1))][j - 1]);
}
}
return;
}
void find(int left, int right)
{
if (right <= left)//左端点大于等于右端点,结束
return;
int l = minpos(left, right);
int r = maxpos(left, right);
if (l <= r)//当前区间有合法最大最小的序列解,当前序列不可能存在于其它序列中,二分
{
ans = ans > r - l + 1 ? ans : r - l + 1;
// printf("%d %d %d\n", l, r, ans);
// getchar();
find(left, l - 1);
find(r + 1, right);
}
else//没有合法解,三分
{
find(r + 1, l - 1);
find(left, r);
find(l, right);
}
return;
}

枚举右端点得到解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <math.h>
#include <stdio.h>
int find(int n);
void rmq(int n);
int a[100005] = {};
int min[100005][20] = {}; //min 里面存放最值的位置
int ans = 0;
int themin(int x, int y)
{
if (a[x] < a[y])
return x;
return y;
}
int minpos(int left, int right)
{
int k = log(right - left + 1) / log(2);
return themin(min[left][k], min[right + 1 - (1 << k)][k]);
}
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
min[i][0] = i;
}
rmq(n);
find(n);
if (ans == 1)
ans = 0;
printf("%d", ans);
return 0;
}
void rmq(int n)
//存入区间最小值的位置
{
int i, j;
for (j = 1; (1 << j) <= n; j++)
{
for (i = 0; i + (1 << j) - 1 < n; i++)
{
min[i][j] = themin(min[i][j - 1], min[i + (1 << (j - 1))][j - 1]);
}
}
return;
}
int find(int n)
//枚举最右端点B,找到左边比它大或者等于的点C,最小值点A一定在C右边,找到区间最小值点是最优解
//然后这时找到的AB区间内不会再有下一个区间比当前区间更优的点B存在
{
for (int i = n - 1; i >= 0; i--)
{
int maxposs = i, minposs;
int j = i - 1;
while (a[maxposs] > a[j] && j >= 0)
j--;
if (j >= 0)
minposs = minpos(j + 1, maxposs);
else
minposs = minpos(0, maxposs);
ans = ans > (maxposs - minposs + 1) ? ans : maxposs - minposs + 1;
//printf(" %d %d %d\n", minposs, maxposs, ans);
i = minposs;
}
return 0;
}