P6477 [NOI Online #2 提高组]子序列问题(民间数据) 题解

博客园同步

原题链接

简要题意:

给定 n n n 个数 a i a_i ai,求 ∑ l = 1 n ∑ r = l n ( f l , r ) 2 \sum_{l=1}^n \sum_{r=l}^n (f_{l,r})^2 l=1nr=ln(fl,r)2 f l , r f_{l,r} fl,r a l , a l + 1 ⋯ a r a_l , a_{l+1} \cdots a_r al,al+1ar不重复数 的个数。

NOI ONLINE 2 \text{NOI ONLINE 2} NOI ONLINE 2 考场 T 2 T2 T2,比较有思维含量的。

Case 1

对于 10 % 10 \% 10% 的数据, n ≤ 10 n \leq 10 n10.

瞎搞分。

Case 2

对于 30 % 30 \% 30% 的数据, n ≤ 100 n \leq 100 n100.

考虑枚举左右端点,然后暴力构造区间,用 map \text{map} map 去重。 O ( n 3 log ⁡ n ) O(n^3 \log n) O(n3logn),得分 30 p t s 30pts 30pts.

考虑一个优化,可以先将原数组 离散化,然后用桶, O ( n 3 ) O(n^3) O(n3),得分 30 p t s 30pts 30pts.

Case 3

对于 50 % 50 \% 50% 的数据, n ≤ 1 0 3 n \leq 10^3 n103.

实际上,对于一个固定的左端点 l l l,可以不断往右延伸,对于每一个新的 r ( l ≤ r ≤ n ) r (l \leq r \leq n) r(lrn) 都只需用桶新维护一个数,这样可以做到 O ( n 2 ) O(n^2) O(n2),得分 50 p t s 50pts 50pts.

实际上,本人在考场上止步于此,深感遗憾!

Case 4

对于 70 % 70 \% 70% 的数据, n ≤ 1 0 5 n \leq 10^5 n105.
对于 100 % 100 \% 100% 的数据, n ≤ 1 0 6 n \leq 10^6 n106 a i ≤ 1 0 9 a_i \leq 10^9 ai109.

首先离散化,降值域。我们需要一个 O ( n n ) O(n \sqrt{n}) O(nn ) O ( n log ⁡ n ) O(n \log n) O(nlogn) 的数据结构。

注意到 平方 不太好维护,所以:

注意到 x 2 = 2 × x × ( x − 1 ) 2 + x x^2 = 2 \times \frac{x \times (x-1)}{2} + x x2=2×2x×(x1)+x,则令 g l , r = f l , r × ( f l , r − 1 ) 2 g_{l,r} = \frac{f_{l,r} \times (f_{l,r}-1)}{2} gl,r=2fl,r×(fl,r1) ,则计算每个区间 g l , r + f l , r g_{l,r}+f_{l,r} gl,r+fl,r 即可。

预处理 L a s t i Last_i Lasti 是满足 a i = a j ( 1 ≤ j < i ) a_i = a_j (1 \leq j < i) ai=aj(1j<i) 中最大的 j j j,不存在则 L a s t i = 0 Last_i=0 Lasti=0

下面枚举 r r r ,只需计算 ∑ l = 1 r g l , r + f l , r \sum_{l=1}^r g_{l,r} + f_{l,r} l=1rgl,r+fl,r.

那么考虑 r → r + 1 r \rightarrow r+1 rr+1 会增加啥?

首先 ∑ f l , r + 1 − f l , r = ( r + 1 ) − L a s t r + 1 \sum f_{l,r+1} - f_{l,r} = (r+1) - Last_{r+1} fl,r+1fl,r=(r+1)Lastr+1,因为 i ∈ [ L a s t r + 1 + 1 , r + 1 ] i \in [Last_{r+1}+1,r+1] i[Lastr+1+1,r+1] 显然 f i , r + 1 − f i , r = 1 f_{i,r+1} - f_{i,r} = 1 fi,r+1fi,r=1,多出了 a r + 1 a_{r+1} ar+1,增加一个。

g l , r g_{l,r} gl,r 怎么搞?

所以用 线段树 维护 f l , r f_{l,r} fl,r 的值,那么显然 r → r + 1 r \rightarrow r+1 rr+1 需要让 [ L a s t r − 1 + 1 , r + 1 ] [Last_{r-1}+1,r+1] [Lastr1+1,r+1] 区间 + 1 +1 +1 即可。

∑ g l , r + 1 − ∑ g l , r \sum g_{l,r+1} - \sum g_{l,r} gl,r+1gl,r 呢?考虑一个公式 x × ( x + 1 ) 2 − x × ( x − 1 ) 2 = x \frac{x \times (x+1)}{2} - \frac{x \times (x-1)}{2} = x 2x×(x+1)2x×(x1)=x,所以:

∑ g l , r + 1 − ∑ g l , r = ∑ l = L a s t r + 1 + 1 r + 1 f l , r \sum g_{l,r+1} - \sum g_{l,r} = \sum_{l=Last_{r+1}+1}^{r+1} f_{l,r} gl,r+1gl,r=l=Lastr+1+1r+1fl,r ,可以继续用 线段树 维护。

注意:最后询问结果 × 2 \times 2 ×2 才是最终 g l , r g_{l,r} gl,r 的值,这个细节让我调试了 0.5 h 0.5h 0.5h.

时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn).

期望得分: 100 p t s 100pts 100pts.

实际得分: 75 75 75 ~ 100 p t s 100pts 100pts.( 1 0 6 10^6 106 如果硬卡你常数的话,因为你线段树要都开 long long \text{long long} long long,不一定能过,要注意常数优化!)

//O3 优化模板
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize(5000)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
//代码中所有 i=-~i 等同于 i++ , 用来卡常
//register int 可视为 int , 用来卡常
//inline 和快读都是用来卡常的
#include
using namespace std;

typedef long long ll;
const ll MOD=1e9+7;
const int N=1e6+1;

#define L i<<1
#define R i<<1|1
#define DEBUG cout<<__LINE__<<" "<<__FUNCTION__<

inline int read(){char ch=getchar(); int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
	int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}

int n,a[N]; bool q[N];
map<int,int> uni; //离散化工具
int b[N],last[N],gett[N]; //gett[i] 维护 last 构成的桶
ll ans=0;

struct tree{
	int l,r; ll tag;
	ll sumi;
} t[N<<2];

inline void update(int i) {
	t[i].sumi=(t[L].sumi+t[R].sumi)%MOD;
}

inline void pass(int i,ll x) {
	t[i].tag=t[i].tag+x;
	t[i].sumi=(t[i].sumi+x*(t[i].r-t[i].l+1))%MOD;
}

inline void pushdown(int i) {
	pass(L,t[i].tag);
	pass(R,t[i].tag);
	t[i].tag=0;
}

inline void build_tree(int i,int l,int r) {
	t[i].l=l; t[i].r=r; t[i].sumi=0; t[i].tag=0;
	if(l==r) return;
	int mid=(l+r)>>1;
	build_tree(L,l,mid);
	build_tree(R,mid+1,r);
//	update(i);
}

inline ll query(int i,int l,int r) {
	if(l<=t[i].l && t[i].r<=r) return t[i].sumi;
	int mid=(t[i].l+t[i].r)>>1; ll ans=0;
	pushdown(i);
	if(l<=mid) ans=(ans+query(L,l,r))%MOD;
	if(r>mid) ans=(ans+query(R,l,r))%MOD;
	return ans; 
}

inline void change(int i,int l,int r,ll x) {
	if(l<=t[i].l && t[i].r<=r) {
		t[i].sumi=(t[i].sumi+x*(t[i].r-t[i].l+1))%MOD;
		t[i].tag=t[i].tag+x; return ;
	} pushdown(i);
	int mid=(t[i].l+t[i].r)>>1;
	if(l<=mid) change(L,l,r,x);
	if(r>mid) change(R,l,r,x);
	update(i);
} //线段树模板

int main() {
	n=read();
	for(register int i=1;i<=n;i=-~i) a[i]=read(),b[i]=a[i];
	sort(b+1,b+1+n); int k=1; uni[b[1]]=1;
	for(register int i=2;i<=n;i=-~i) {
		if(b[i]!=b[i-1]) k++;
		uni[b[i]]=k;
	}
	for(register int i=1;i<=n;i=-~i) a[i]=uni[a[i]];
	for(register int i=1;i<=n;i=-~i) {
		last[i]=gett[a[i]];
		gett[a[i]]=i;
//		printf("%d %d\n",a[i],last[i]);
	} ll s=0; build_tree(1,1,n);
	for(register int r=0;r<n;r++) {
		ll t=r+1-last[r+1]; //ans=(ans+r-last[r])%MOD;
		t=(t+query(1,last[r+1]+1,r+1)*2)%MOD; //t 为增加答案
		change(1,last[r+1]+1,r+1,1);
		s=(s+t)%MOD; ans=(ans+s)%MOD; //s 为当前贡献,ans 为总答案,注意取模
	} printf("%lld\n",ans);
	return 0;
}

你可能感兴趣的:(数论,线段树)