UVa11536 Smallest Sub-Array解题报告

题目链接
  又是一个优化枚举次数的题目,这次一定要记录下来,学会这种解题思路。

题目分析

  题目的意思是,需要在一个数组中,找到包含$[1,K]$中所有整数的最短长度的子数组。
  当时我的思路是通过枚举起点,然后通过二分法来找到终点。然后超时了。。。。然后发现是我的枚举对象搞错了,以后解题要首先明确枚举对象是什么。
  根据网上的解题报告,可以选满足条件的最短长度作为枚举对象,然后二分查找,由于满足条件的长度$1\leq len \leq N$ ,每次枚举后通过滑动窗口来判断是否满足条件,时间复杂度为$O(N)$,所以总共的时间复杂度为$O(N\log N)$

算法设计

  取枚举区间为左闭右开区间,初始L=1、R=N+1。如果当前枚举的值满足条件说明还可以继续往小了分,否则就应该往大了分。
  判断是否满足条件用滑动窗口法,假设当前枚举长度为$len$,那么我们通过扫描一遍数组,每次只需要判断右边滑进来的元素、和左边滑出去的元素,这样就可以在$O(N)$时间内得到结果。

完整代码

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>

using namespace std;
#define INF 0x3f3f3f3f
const int maxn = 1000000 + 10000;
const int maxk = 1000+200;

int seq[maxn];

int cnt[maxk];
//前缀和


int N, M, K;

//尝试用长度为len的滑动窗口
bool judge(int len)
{
//用来保存在滑动窗口中出现数字的总数
int tot = 0;
//cnt[i]记录出现数字i的次数
memset(cnt, 0, sizeof(cnt));

//滑动窗口
for (int i = 1; i <= N; i++)
{
if (tot == K) return true;
if (i == N + 1) return false;
//滑动窗口新加入的元素
if (seq[i] <= K&&cnt[seq[i]]++ == 0) tot++;
//滑动窗口滑出去的元素
if (i > len&&seq[i - len] <= K&&--cnt[seq[i - len]] == 0) tot--;
}
if (tot == K) return true;
return false;
}


//二分枚举答案的长度
int solve()
{
int L = 1;
int R = N + 1;

while (L < R)
{
int mid = (L + R) / 2;
if (judge(mid)) R = mid;
else L = mid + 1;
//printf("mid = %d\n", mid);
}
if (L == N + 1) return -1;

return L;
}

void init()
{


seq[1] = 1;
seq[2] = 2;
seq[3] = 3;


for (int i = 4; i <= N; i++)
{
seq[i] = (seq[i - 1] + seq[i - 2] + seq[i - 3]) % M + 1;

}

}

int main()
{
int T;
int kcase = 1;
freopen("C:\\Users\\lenovo\\Desktop\\test\\in.txt", "r", stdin);
freopen("C:\\Users\\lenovo\\Desktop\\test\\out.txt", "w", stdout);
scanf("%d", &T);

while (T--)
{
scanf("%d%d%d", &N, &M, &K);
//printf("N = %d M = %d K=%d\n", N, M, K);
init();
int min_len = solve();
if (min_len > 0)
printf("Case %d: %d\n", kcase++, min_len);
else
printf("Case %d: sequence nai\n", kcase++);
}

return 0;
}

以后记得选取正确的枚举对象~