NOI Online #2 提高组 T2 子序列问题

原题链接

简洁题意

求一个序列的任意一个子串(由连续的数组成)组成的集合大小之和(集合过滤重复元素)。

分析:

这个题可以思考,要想某一个数在一个字串中有贡献,当且仅当它是该集合第一个出现的数。所以,可以统计一下上一个出现的位置, i i i到这个位置后面都能有贡献。用 d d d数组存每个 a i a_i ai上一次出现的位置。因为 a a a有点大,数组存不下,可以考虑 m a p map map或者离散化。
现在,设 g ( r ) = ∑ l = 1 r f ( l , r ) 2 g(r)=\displaystyle\sum_{l=1}^r f(l,r)^2 g(r)=l=1rf(l,r)2,则可以枚举每一个 r r r计算之间的变化。计算增加一个 a r a_r ar会有什么变化,则计算 g ( r ) − g ( r − 1 ) g(r)-g(r-1) g(r)g(r1)即可。我们假定 f ( l , r ) = 0 , l > r f(l,r)=0,l>r f(l,r)=0,l>r = ∑ l = 1 r f ( l , r ) 2 − ∑ l = 1 r − 1 f ( l , r − 1 ) 2 =\sum_{l=1}^{r}f(l,r)^2-\sum_{l=1}^{r-1}f(l,r-1)^2 =l=1rf(l,r)2l=1r1f(l,r1)2只有 l a s t r + 1 last_r+1 lastr+1 r r r的区间减下来是有值的,因此原式得 = ∑ l = l a s t r + 1 r ( f ( l , r ) 2 − f ( l , r − 1 ) 2 ) =\sum_{l=last_r+1}^r (f(l,r)^2-f(l,r-1)^2) =l=lastr+1r(f(l,r)2f(l,r1)2)因为两个平方底数的差一定是 1 1 1,则 = ∑ l = l a s t r + 1 r ( 2 f ( l , r − 1 ) + 1 ) =\sum_{l=last_r+1}^r (2f(l,r-1)+1) =l=lastr+1r(2f(l,r1)+1) = ∑ l = l a s t r + 1 r 2 f ( l , r − 1 ) + r − l a s t r =\sum_{l=last_r+1}^r 2f(l,r-1)+r-last_r =l=lastr+1r2f(l,r1)+rlastr我们每次只用加上这个值即可。
后面非求和的部分可以直接计算,那么前面 f f f求和的地方怎么算呢?可以利用差分,在 l a s t r + 1 last_r+1 lastr+1加一, i + 1 i+1 i+1减一,这样这个区间的 f f f和就已经计算了,这样求一个前缀和即可。求前缀和用树状数组。因为我们 f f f求的是 f ( l , r − 1 ) f(l,r-1) f(l,r1),则要在求前缀和之后加上差分标记。最后,答案就是 ∑ r = 1 n g ( r ) \displaystyle\sum_{r=1}^{n}g(r) r=1ng(r)
每次操作时间复杂度是 Θ ( log ⁡ n ) \Theta(\log n) Θ(logn)的,总计是 Θ ( n log ⁡ n ) \Theta(n\log n) Θ(nlogn)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int NN=1000005,P=1e9+7;
ll t1[NN],t2[NN];
int a[NN],b[NN],d[NN],last[NN];
ll lowbit(int x)
{
	return x&-x;
}
void add(int x,int d,int n)
{
	int y=x;
    while(y<=n)
	{
        t1[y]+=d;
        t2[y]+=1ll*d*x;
        y+=lowbit(y);
    }
}
ll sum(int x)
{
    ll res=0;
    int y=x;
    while(y)
    {
        res+=t1[y]*(x+1)-t2[y];
    	y-=lowbit(y);
	}
    return res;
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
	{
        scanf("%d",&a[i]);
        b[i-1]=a[i];
    }
    sort(b,b+n);
    int m=unique(b,b+n)-b;
    for(int i=1;i<=n;i++)
	{
        a[i]=lower_bound(b,b+m,a[i])-b;
        last[i]=d[a[i]];
        d[a[i]]=i;
    }
    ll ans=0,now=0;
    for(int i=1;i<=n;i++)
	{
        (now+=i-last[i]+2*(sum(i)-sum(last[i])))%=P;
        ans+=now;
        add(last[i]+1,1,n);
        add(i+1,-1,n);
    }
    printf("%lld",ans%P);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值