第1行:一个数N, 表示数组的长度(1 <= N <= 50000)。 第2 - N + 1行:每行1个数,对应数组中的元素Ai。(1 <= Ai <= 10^9)
输出符合条件的集合数量。
案例解释:
下标: 0 1 2 3 4 5
val : 3 5 7 3 3 5
有14对:
(1, 4), (1, 3), (2, 2), (2, 1), (2, 0), (3, 2), (3, 1), (3, 0), (4, 2), (4, 1), (4, 0), (5, 2), (5, 1), (5, 0)
0 - 1 , 4 - 5 都出现了 3 5
0 - 1, 3 - 5 都出现了3 5
....
....
....
其他自己看吧
这个题又2种做法:
第一种是我这种菜鸡的存每个前缀的个数,然后从后面扫后缀,ans += 该后缀对应的前缀个数
对于每个前缀可以利用一个 Hash 值存下来 , 过于简单的思想,看代码吧:
#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
const int M =1e5 + 50;
#define mst(a,b) memset(a,b,sizeof(a))
#define rush() int T;cin>>T;while(T--)
map<int,bool> mp;
map<pair<int,int>,int> pre;
int a[M];
int main() {
int n,k = 0,kk = 0;
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]);
if(mp[a[i]] == 0) {
k = k + a[i] * 131 % mod;
kk = kk + a[i] % mod * a[i] % mod;
mp[a[i]] = 1;
}
pre[{k,kk}]++;
}
mp.clear();
k = kk = 0;
long long ans = 0;
for(int i=n;i>=1;i--) {
if(mp[a[i]] == 0) {
k = k + a[i] * 131 % mod;
kk = kk + a[i] % mod * a[i] % mod;
mp[a[i]] = 1;
}
ans += pre[{k,kk}];
}
cout<<ans;
}
然后就是更精彩的思维了,(看大佬代码习得)
这个题如果要用O(n) 的算法
我们可以遍历前缀,然后统计每个前缀的贡献值
怎么算他们的贡献值呢?
那就是计算后缀相同的个数,然后利用一个 j 指向n,表示后缀,
这个 j 移动规则是:遇到前缀里面出现了的数 j--
否则 break;
这样移动是为了 1.保证 O(n)
2.保证后缀里面出现的数都是前缀里面的
但是这样无法保证前缀和后缀出现的数字刚好一样,
可能前缀有几个数,后缀里面没有。
那么可以利用 map 去记录每个数最后出现的坐标,
如果前缀里面出现的数里面最前面的坐标pre_pos(也就是后缀 j 最难到达的地方)
如果说 j 到达了pre_pos 说明这 2 个序列是一样的 ( 前缀里面最难到达的数到达了,其他数也肯定有)
然后就看他们的贡献值
这个值不是单纯的 1 ,而是 pre_pos 那个界限值 和 j 的距离中间每一个都可以贡献一个
即 j - pre_pos
贴代码:(如果有什么看不懂的,推荐多看看代码,再来体会一下这种写法的意思)
#include<bits/stdc++.h>
#include<map>
using namespace std;
const int M =5e5 + 50;
map<int,int> pos,vis;
int a[M];
int main() {
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]);
pos[a[i]] = max(pos[a[i]],i);
}
// for(auto it:pos) cout<< it.first << ' ' << it.second << endl;
long long ans = 0;
int j = n,pre_pos = 1e9;
for(int i=1;i<=n;i++) {
vis[a[i]] = 1;
pre_pos = min(pre_pos,pos[a[i]]);
for(;j>=1;j--) {
if(!vis[a[j]]) {
break;
}
}
if(j < pre_pos)
ans += 1ll * pre_pos - j;
}
cout << ans;
}