题解 洛谷P6477 [NOI Online #2 提高组] 子序列问题
朴素的做法是枚举左、右端点。用\(\texttt{set}\)维护区间内不同值的数量,时间复杂度\(O(n^2\log n)\)。
考虑优化这个做法,就必须避免枚举左、右端点。一种想法是,枚举\(f(l,r)\)的值,然后计算这个值的出现次数。这是经典的算贡献的思想,但是似乎无法快速求出一个\(f(l,r)\)的值在多少区间里出现过。
直接算\(f\)值的贡献行不通,转而考虑计算序列里每个位置对答案的贡献。可以认为,一个数能贡献到\(f(l,r)\)中,当且仅当它是该值在\([l,r]\)区间里第一次出现。我们记录下序列里每个位置上的数上一次出现的位置,记为\(\text{pre}[i]\)。特别地,如果该位置上的数是在序列里第一次出现,则\(\text{pre}[i]=0\)。这样,位置\(i\)上的数,会贡献到\(l\in(pre[i],i], r\in[i,n]\)的这些区间的\(f\)值中。
但是,\(f\)值并不是直接累加到答案里,而是要先平方,再累加到答案。如何处理这个平方呢?我们知道,\(f(l,r)\)相当于区间里每个值第一次出现的这些位置。而\((f(l,r))^2\),就相当于在这些位置中,任选出两个位置(可以重复)的方案数!
我们枚举,任选出的这两个位置,设它们分别为\((i,j)\)(不妨设\((i\leq j)\))。则一对\((i,j)\)会对多少个区间产生贡献?不难发现,数量是:
分别是可选择的左端点的数量,右端点的数量。
暴力枚举\((i,j)\),按上述式子计算,复杂度为\(O(n^2)\)。
但这个式子是很好优化的。我们可以枚举\(j\)。问题转化为,如何快速求出:\(\sum_{i=1}^{j}\max(\text{pre}[i],\text{pre}[j])\)。我们用一个变量,记录\(j\)前面所有\(\text{pre}[i]\)之和,则只需要把\(\leq\text{pre}[j]\)的这部分值减去,再加上相同数量的\(\text{pre}[j]\)即可。可以用两个树状数组,都以\(\text{pre}\)值为下标,分别维护\(\text{pre}\)值小于\(x\)的\(\text{pre}\)值之和,及其数量。
时间复杂度\(O(n\log n)\)。
参考代码:
//problem:sequence
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const int MAXN=1e6,MOD=1e9+7;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int& x,int y){x=mod1(x+y);}
inline void sub(int& x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
int n,a[MAXN+5],vals[MAXN+5],cnt_v,pre[MAXN+5],pos[MAXN+5],tosub[MAXN+5];
vector<int>vec[MAXN+5];
struct FenwickTree{
int sum[MAXN+5],num[MAXN+5];
void point_add(int p,int ds,int dn){
p++;
for(;p<=n;p+=(p&(-p)))add(sum[p],ds),add(num[p],dn);
}
pii prefix_query(int p){
p++;
int rs=0,rn=0;
for(;p;p-=(p&(-p)))add(rs,sum[p]),add(rn,num[p]);
return mk(rs,rn);
}
FenwickTree(){}
}T;
int main() {
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
cin>>n;
for(int i=1;i<=n;++i)cin>>a[i],vals[i]=a[i];
sort(vals+1,vals+n+1);
cnt_v=unique(vals+1,vals+n+1)-(vals+1);
for(int i=1;i<=n;++i){
a[i]=lob(vals+1,vals+cnt_v+1,a[i])-vals;
}
for(int i=1;i<=n;++i){
pre[i]=pos[a[i]];
pos[a[i]]=i;
vec[pre[i]].pb(i);
}
int ans=0;
/*
for(int i=1;i<=n;++i){
for(int j=pre[i]+1;j<=i;++j){
add(ans,(ll)(j-max(pre[i],pre[j]))*(n-i+1)%MOD*(i==j?1:2)%MOD);
}
}
*/
for(int i=1,sumpre=0;i<=n;++i){
if(i>1){
int x=(ll)i*(i-1)/2%MOD;
sub(x,sumpre);
pii q=T.prefix_query(pre[i]);
add(x,q.fi);
sub(x,(ll)q.se*pre[i]%MOD);
x=(ll)x*(n-i+1)*2%MOD;
sub(x,tosub[i]);
add(ans,x);
}
add(ans,(ll)(i-pre[i])*(n-i+1)%MOD);
add(sumpre,pre[i]);
T.point_add(pre[i],pre[i],1);
for(int j=0;j<SZ(vec[i]);++j){
int ii=vec[i][j];
int x=(ll)i*(i+1)/2%MOD;
sub(x,sumpre);
pii q=T.prefix_query(pre[ii]);
add(x,q.fi);
sub(x,(ll)q.se*pre[ii]%MOD);
x=(ll)x*(n-ii+1)*2%MOD;
add(tosub[ii],x);
}
}
cout<<ans<<endl;
return 0;
}