出题题解
简要题意
给定一个长度为 nnn 的序列,求区间两端数字均不与区间中其他数字相等(包括另一端)的区间数量,且长度不小于二。
解析
先考虑一下最好办的情况,显然就是所有人的年级都不相等。
比如 1 3 2 4 6 7 8
,如何统计?
可以直接写结论,但我们不妨这样看:(1,2)显然只有一种,加上了一个2之后(1,3)显然有三种,.....,在这种情况下,答案显然就是0+1+2+.....+i-1
(可能有助于接下来的理解)。
回到样例 1 2 3 4 3 2 5
,沿用上文的方式,我们从1开始遍历:
b[i]] | 1 | 2 | 3 | 4 | 3 | 2 | 7 |
---|---|---|---|---|---|---|---|
ans | 0 | 1 | 3 | 6 |
到了第二个3处,不能采用之前的统计方法了,暴力统计会T飞,先暂时无视这个3,继续统计答案,于是:
b[i]] | 1 | 2 | 3 | 4 | 3 | 2 | 7 |
---|---|---|---|---|---|---|---|
ans | 0 | 1 | 3 | 6 | 10 |
但很多区间是不可以的,比如(3,5),(2,5)等,而这些均取决于上一次相同数字出现的位置,因为它限制住了当前数字到之前的区间最大长度。
题目中告诉我们,$ bi≤n≤2×105b_i \le n \le 2\times 10^5bi≤n≤2×105 $,所以可以引入 s[b]
来记录每个数字上一次出现的位置。
如果一个数字出现多次呢?
显然我们只用管下标离当前年级最近的就好了,如果要囊括一个包含同一个数字不止一个的区间,更前面的由于后面的同年级的阻碍自然是无法与更后面的其他年级成组。
要做的也很简单,只要 s[b]=i
即可。
回到最开始的方法,可以感性理解为每个数字初始都有另外一个权值1,最简单的情况其实就是在不断加上前 i-1
个数字的那个权值,于是就 1 3 6 10....
。
再看样例,3多加的部分如何去除呢?
由于第一个3的隔断,因此后一个3无法与前面的 1 2 3
发生联系,所以减去1—s[b]
的权值即可。
b[i]] | 1 | 2 | 3 | 4 | 3 | 2 | 7 |
---|---|---|---|---|---|---|---|
ans | 0 | 1 | 3 | 6 | 10->7 |
记得更新s[b]
b | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
s[b] | 1 | 2 | 5 | 4 | ? | ? | ? |
到了下一个2哪里也是这样,不过中间还有一步,如果还要按照以上步骤来,会出现一个问题,(3,6)这个区间无法解释。问题出在哪里呢?第一个3所造成的影响与他之后所有的数字都是有影响的,为此必须将第一个3的权值减一归零,因为(3,i)这个情况对于后面的所有数字都是不可能的,必须剔除这种情况。 最后就是这样:
b[i]] | 1 | 2 | 3 | 4 | 3 | 2 | 7 |
---|---|---|---|---|---|---|---|
ans | 0 | 1 | 3 | 6 | 10->7 | 11->9 | 14 |
b | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
s[b] | 1 | 6 | 5 | 4 | ? | ? | 7 |
综合来看,这是一个需要区间查询,单点修改的东西,所以祭上一棵线段树。
tips1:文中区间一律为闭区间,区间中的数字为下标。
tips2:数字、年级两个词混着用,但意思应该都是一个意思。
如果发现文中错误或者看出我的做法哪里假了却误打误撞对了,欢迎指正。
最后贴一下代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
#define int long long//40pts!!!!
int n,ans;
int s[maxn];
struct node {
int add,sum,l,r;
} t[maxn*4];
/*
按照分析的思路其实更容易想到树状数组,但我写的还是线段树,可能是不怎么写树状数组吧。
*/
void build(int p,int l,int r) {
t[p].l=l;
t[p].r=r;
if(l==r) {
t[p].sum=0;
return ;
}
int mid=(l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
t[p].sum=t[p*2].sum+t[p*2+1].sum;
}
void add(int p,int pos,int k) {
if(t[p].l==t[p].r) {
t[p].sum+=k;
return ;
}
if(pos<=t[p*2].r)
add(p*2,pos,k);
else
add(p*2+1,pos,k);
t[p].sum=t[p*2].sum+t[p*2+1].sum;
return;
}
int ask(int p,int l,int r) {
int ans=0;
if(t[p].l>=l && t[p].r<=r)
return t[p].sum;
if(t[p].r<l or t[p].l>r) return 0;
if(t[p*2].r>=l) ans+=ask(p*2,l,r);
if(t[p*2+1].l<=r) ans+=ask(p*2+1,l,r);
return ans;
}
signed main() {
cin>>n;
build(1,1,n);
for (int i=1; i<=n; i++) {
int b;
cin>>b;
ans+=ask(1,1,i-1);
if (s[b]) {//如果之前有相同年级的出现过
ans-=ask(1,1,s[b]);
add(1,s[b],-1);//这个操作的表述不太好,大家看看得了。
}
add(1,i,1);//这个就是所谓的'权值 '
s[b]=i;
}
cout<<ans<<endl;
return 0;
}