题目描述
难度分:1400
输入T(≤104)表示T组数据。所有数据的n之和≤2×105。每组数据输入n(1≤n≤105)和长为n的数组a(1≤a[i]≤109)。
输出a有多少个非空连续子数组b,满足b是a的唯一子序列(不存在其他的子序列等于b)。
输入样例
6
1
1
2
1 1
3
1 2 1
4
2 3 2 1
5
4 5 4 5 4
10
1 7 7 2 3 4 3 2 1 100
输出样例
1
1
4
7
4
28
算法
思维题
感觉还是蛮难的,关键是分析唯一需要满足什么性质。观察一下样例就可以发现,只有第一次出现的数值x和最后一次出现的数值y之间这一段子数组才可能唯一,否则如果左边还有x,第一个位置就可以替换成更左边的x,如果右边还有y,最后一个位置就可以替换成更右边的y,且不影响子序列的内容。
先预处理出每个元素值第一次的出现位置和最后一次的出现位置,存入到两个哈希表first和last中,并将last中的索引都转存到一个数组pos中排好序。然后遍历i∈[1,n],如果first[a[i]]=i,就开始统计以a[i]开头的子数组对应的子序列有多少个满足在整个数组中只出现一次。这时候二分一下pos数组,看看有多少个最后出现索引j≥i,这些j所决定的子数组[i,j]都能满足相应子序列在整个数组中只出现一次。
复杂度分析
时间复杂度
预处理出两个哈希表first和last只需要遍历一遍数组,时间复杂度O(n)。遍历一遍last表,预处理出pos数组时间复杂度O(n),对pos数组排序时间复杂度O(nlog2n)。最后遍历一遍数组求答案,对于每个满足first[a[i]]=i的i,都要在pos数组上做一次二分查找,时间复杂度为O(log2n)。
综上,整个算法的时间复杂度为O(nlog2n)。
空间复杂度
空间瓶颈在于两个哈希表first和last,以及pos数组,它们都是线性空间。因此,整个算法的额外空间复杂度为O(n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010;
int T, n, a[N];
struct custom_hash {
static uint64_t splitmix64(uint64_t x) {
x += 0x9e3779b97f4a7c15;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
return x ^ (x >> 31);
}
size_t operator()(uint64_t x) const {
static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count();
return splitmix64(x + FIXED_RANDOM);
}
};
int main() {
scanf("%d", &T);
while(T--) {
scanf("%d", &n);
unordered_map<int, int, custom_hash> last, first;
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
last[a[i]] = i;
if(!first.count(a[i])) first[a[i]] = i;
}
vector<int> pos;
for(auto&[num, x]: last) {
pos.push_back(x);
}
sort(pos.begin(), pos.end());
LL ans = 0;
for(int i = 1; i <= n; i++) {
if(i == first[a[i]]) {
ans += pos.size() - (lower_bound(pos.begin(), pos.end(), i) - pos.begin());
}
}
printf("%lld\n", ans);
}
return 0;
}