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


    题目链接

    朴素的做法是枚举左、右端点。用( exttt{set})维护区间内不同值的数量,时间复杂度(O(n^2log n))

    考虑优化这个做法,就必须避免枚举左、右端点。一种想法是,枚举(f(l,r))的值,然后计算这个值的出现次数。这是经典的算贡献的思想,但是似乎无法快速求出一个(f(l,r))的值在多少区间里出现过。

    直接算(f)值的贡献行不通,转而考虑计算序列里每个位置对答案的贡献。可以认为,一个数能贡献到(f(l,r))中,当且仅当它是该值在([l,r])区间里第一次出现。我们记录下序列里每个位置上的数上一次出现的位置,记为( ext{pre}[i])。特别地,如果该位置上的数是在序列里第一次出现,则( ext{pre}[i]=0)。这样,位置(i)上的数,会贡献到(lin(pre[i],i], rin[i,n])的这些区间的(f)值中。

    但是,(f)值并不是直接累加到答案里,而是要先平方,再累加到答案。如何处理这个平方呢?我们知道,(f(l,r))相当于区间里每个值第一次出现的这些位置。而((f(l,r))^2),就相当于在这些位置中,任选出两个位置(可以重复)的方案数!

    我们枚举,任选出的这两个位置,设它们分别为((i,j))(不妨设((ileq j)))。则一对((i,j))会对多少个区间产生贡献?不难发现,数量是:

    [(i-max( ext{pre}[i], ext{pre}[j]))cdot (n-j+1)quad (i> ext{pre}[j]) ]

    分别是可选择的左端点的数量,右端点的数量。

    暴力枚举((i,j)),按上述式子计算,复杂度为(O(n^2))

    但这个式子是很好优化的。我们可以枚举(j)。问题转化为,如何快速求出:(sum_{i=1}^{j}max( ext{pre}[i], ext{pre}[j]))。我们用一个变量,记录(j)前面所有( ext{pre}[i])之和,则只需要把(leq ext{pre}[j])的这部分值减去,再加上相同数量的( ext{pre}[j])即可。可以用两个树状数组,都以( ext{pre})值为下标,分别维护( ext{pre})值小于(x)( ext{pre})值之和,及其数量。

    时间复杂度(O(nlog 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;
    }
    
  • 相关阅读:
    Android SHA1与Package获取方式
    《C++语言基础》实践參考——数组作数据成员
    Linux进程间通信——使用共享内存
    Java中ArrayList和LinkedList差别
    hdu1695(莫比乌斯)或欧拉函数+容斥
    Android学习路线(二十四)ActionBar Fragment运用最佳实践
    HTML——使用表格对表单进行布局
    Nginx 訪问日志增长暴增出现尖刀的具体分析
    基于特定值来推断隐藏显示元素的jQuery插件
    怎样配置PHP环境和安装Zendstdio编辑器
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/12772729.html
Copyright © 2020-2023  润新知