序列问题

Description

Input

输入文件名为seq.in。
首先输入n。
接下来输入n个数,描述序列 A。

Output

输出文件名为seq.out。
输出一行一个整数代表答案。

Sample Input

7
0 35 40 45 56 65 94

Sample Output

66636

Data Constraint

对于30%的数据,n<=5000
对于60%的数据,n<=50000
对于100%的数据,n<=500000,0<=A[i]<=10^9
 

Source / Author: 翁文涛

 

题解:

考虑分治,sol(l,r)为l~r的贡献

sol(l,r) = sol(l,mid)+sol(mid+1,r) +\sum f[x][y]*g[x][y](x\epsilon [l,mid]~,~y\epsilon [mid+1,r])

前面两个直接递归下去,重点是后面的,也就是两个指针分别在mid两边的情况。

我们先预处理出min[r] , max[r]表示mid+1 ~ r的最小值和最大值,sumin[r]表示mid+1 ~ r的最小值前缀和,sum[r]表示mid+1 ~ r的最大值*最小值的前缀和。

然后枚举左端点l,一边做一边记录l~mid的最小值和最大值min_l,max_l

对于当前的min_l,我们找到u,使得min[u]恰小于min_l

对于当前的max_l,我们找到v,使得max[v]恰大于max_l

(具体:随着l往左,min_l减小,要使min[u]小于min_l,只会往右

max同理

因此u,v两个指针维护即可

如图所示,我们假设u在v左边,这时候右端点r有三种情况。

(左闭右开)

  1. mid~u :最大值与最小值是min_l,max_l , r在这段区间的贡献总和为(u-mid-1)*max_l*min_l
  2. u~v :最小值不确定 (因为过了点u), 最大值是max_l。但我们知道这段区间的贡献为maxl*min[u] + maxl*min[u+1]+...+maxl*min[v-1]
  3. v~R :最大值和最小值都不确定(点u、v都过了)。但我们知道这段区间的贡献为min[v]*max[v]+min[v+1]*max[v+1]..+min[R]*max[R]

发现之前我们维护的数组sumin和sum数组有用场了(见上面)。对于第2种情况,提出maxl,就可以变成maxl*(sumin[v-1] - sumin[u-1]);

对于第3种,不和l~mid这段区间有关(我们固定了左端点),直接是sum[R]-sum[v-1];

v<u同理

自此问题迎刃而解。

O(log n *n)

#include<bits/stdc++.h>
#define N 500010
#define inf 2147483647
#define rint register ll
#define ll long long
#define point(a) multiset<a>::iterator 
#define mod (ll)(1e9+7)
#define mem(a,b) memset(a,b,sizeof (a))
#define open(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout);
using namespace std;

ll t,ans,n,i;
ll minn[N],maxx[N],sumin[N],sum[N],a[N],sumax[N];

ll sol(ll L,ll R)
{
	if(L>R)return 0;
	if(L==R)return a[L]*a[R];
	ll ans=0;
	ll mid=(L+R)/2;
	maxx[mid]=0;
	minn[mid]=inf;
	sumin[mid]=sumax[mid]=sum[mid]=0;
	//init()
	for(ll r=mid+1;r<=R;r++)
	{
		minn[r] = min(minn[r-1] , a[r]);
		maxx[r] = max(maxx[r-1] , a[r]);
		(sum[r]  = sum[r-1] + minn[r] *maxx[r]%mod)%=mod;
		(sumin[r]  = sumin[r-1]+minn[r])%=mod;
		(sumax[r] = sumax[r-1]+maxx[r])%=mod;
	}
	ll u=mid+1,v=mid+1,max_l=-inf,min_l=inf;
	for(ll l=mid;l>=L;--l)
	{
		min_l=min(min_l,a[l]);
		max_l=max(max_l,a[l]);
		while(u<=R && minn[u]>=min_l)++u;
		while(v<=R && maxx[v]<=max_l)++v;
		if(u<=v)
		{
			(ans+=min_l*max_l%mod*(u-mid-1)%mod)%=mod;
			(ans+=max_l*(sumin[v-1] - sumin[u-1])%mod)%=mod;
			(ans+=(sum[R] - sum[v-1])%mod)%=mod;
		}else
		{
			(ans+=min_l*max_l%mod*(v-mid-1)%mod)%=mod;
			(ans+=min_l*(sumax[u-1] - sumax[v-1])%mod)%=mod;
			(ans+=(sum[R] - sum[u-1])%mod)%=mod;
		}
	
	}
	return  ((ans+sol(L,mid)%mod) +sol(mid+1,R))%mod;
}
void read(ll &x)
{
	char ch=getchar();
	x=0;
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
	return ;
}

void scan()
{
	read(n);
	for(i=1;i<=n;i++)read(a[i]);
}
int main()
{
	
	open("seq");
	scan();
	ans=sol(1,n)%mod;
	printf("%lld\n",ans);
	return 0;
}

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值