题解 洛谷P6477 [NOI Online 2 提高组] 子序列问题

时间:2020-04-25
本文章向大家介绍题解 洛谷P6477 [NOI Online 2 提高组] 子序列问题,主要包括题解 洛谷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-\max(\text{pre}[i],\text{pre}[j]))\cdot (n-j+1)\quad (i>\text{pre}[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;
}

原文地址:https://www.cnblogs.com/dysyn1314/p/12772729.html