【题意】
为了准备考试,Jessica开始读一本很厚的课本。要想通过考试,必须把课本中所有的知识点都掌握。这本书总共n页,第i页恰好有一个知识点
限制条件:
1≤n≤106
【提炼】
计算一段连续的包含所有知识点的子序列的最小长度。(知识点至少出现一次)
【分析】
连续求最小长度,除了dp,首先想到尺取法。
将原问题:求覆盖到所有知识点区间[s,t]的长度的最小值,
根据尺取法的特点,可以将问题转换为:
已知[s,t],求下一个区间覆盖到所有知识点的区间[s+1,t′],这时,必须满足t′≥t。
由以上等价关系,将问题转换为:
从区间的最开始把s取出之后,直到某个知识点出现次数变为0,然后不停将区间末尾t向后推进,重新计算知识点出现的次数以及记录满足条件的最小长度。最终,当t到达数据尾部,算法执行结束。
暴力需要O(n2)
用map存储[s,t]区间上的每个知识点的出现次数,问题又转化为:
从区间的最开始把s取出之后,将该知识点出现次数减1,若该知识点出现次数为0,在同一知识点再次出现前,不停地将区间末尾t向后移动。每次在区间末尾追加页数t时将t所在的知识点的出现次数加1,这样就完成了下一个区间上各知识点的出现次数的更新。最后,遍历一遍全部区间求出最小区间需要O(nlogn)。
【时间复杂度】
由于使用了二叉树的存储结构来保存每个知识点的出现次数,时间复杂度为
【代码】
/*
coder: Tangent Chang
date: 2017/5/9
A day is a miniature of eternity. by Emerson
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <string>
#include <algorithm>
#include <set>
#include <map>
using namespace std;
const int maxn = 1000005;
int d[maxn];
set<int> ST;
map<int , int> MP;
int main() {
int n;
scanf("%d", &n);
for (int i = 0; i < n; ++i) {
scanf("%d", &d[i]);
ST.insert(d[i]);
}
int tmp = n;
n = ST.size();
int s = 0, t = 0, num = 0, res = tmp + 1;
while (1) {
while (t < tmp && num < n) {
if (MP[d[t++]]++ == 0) {
num++;
}
}
if (num < n) break;
res = res < t - s ? res : t - s;
if (--MP[d[s++]] == 0) {
num--;
}
}
printf("%d\n", res);
return 0;
}