P9681 幽默的世界 题解

题目传送门:P9681 幽默的世界。

时间复杂度 \(O(n+q)\)

卡常后最优解

蒟蒻水平有限,讲不太清楚,请见谅。


定义:

int f_ans[200005]; //f_ans 前缀和数组,记录 i 之前合法区间与包含 i 的合法区间数量之和
int dis[200005]; //dis 记录包含 i 的合法区间 且区间右端点不为 i 的合法区间的数量之和
int id[200005];//id 记录这个点所处的块的编号
int tot;//编号

解释:块为以点 \(tot\) 为右端点的合法区间的编号。


解法:

因为题目保证当 \(a\) 的一个连续子序列是幽默的时,有两个性质:

  • \(\sum\limits_{i=l}^ra_i>0\)

  • 对于所有 \(l\le x\le y<r\),都满足 \(\sum\limits_{i=x}^y a_i\le 0\)

区间合法时,我们不妨先从第二个性质下手。

\(x=y\) 时,式子退化成 \(a_x \le 0\),即保证 \(l\le i <r\) 时,\(a_x \le 0\)

再看第一个性质:

第一种情况:\(l<r\),因为 \(\sum\limits_{i=l}^ra_i>0\),且 \(\sum\limits_{i=l}^{r-1}a_i \le 0\),所以 \(a_r\) 必定大于 \(0\)

第二种情况:\(l=r\),即只需满足第一个性质,\(a_r>0\),此区间就合法。

因为 \(a_r>0\)\(\sum\limits_{i=x}^{y} a_i\le 0\) 恒成立,可得每个答案区间不相交

一句话题意:给定 \(l,r\),求仅有最后一项为正数,其它数都小于等于 \(0\) 且最后一项要大于除了最后一项以外其它项之和的绝对值的序列的个数。

可以先枚举区间右端点 \(r\),再枚举从这个端点最远可以到哪个左端点 \(l\)

然后,对于 \(l \le i \le r\),进行更改:

f_ans[i]+=k,dis[i]+=k,id[i]=tot;

每个变量的意思在定义的注释中。

统计答案时,

  • 若点 \(l\)\(r\) 同属一个区间且 \(a_r\le 0\),答案为 \(0\)

  • 若点 \(l\)\(r\) 不属于同一个区间且 \(a_r \le 0\),答案为 f_ans[r]-f_ans[l-1]-dis[r],减 \(dis_r\) 是因为前缀和记录了包含点 \(r\) 的区间数量,但 \(a_r \le 0\),不能作为区间右端点,那么前缀和多加了 \(dis_r\),将其减去即可;

  • 若点 \(l\)\(r\) 同属一个区间且 \(a_r > 0\),或点 \(l\)\(r\) 不属于同一个区间且 \(a_r > 0\),直接输出 f_ans[r]-f_ans[l-1] 即可。


代码:

#include<bits/stdc++.h>
#define int long long
using namespace std; 

int n,q;
int a[200005];//输入数列
int f_ans[200005];
//f_ans 前缀和数组,记录 i 之前合法区间与包含 i 的区间数量之和
int dis[200005];
//dis 记录包含 i 的区间 且区间右端点不为 i 的区间的数量之和
int id[200005];//id 记录这个点所处的块的编号
int tot;//编号(可能不连续)

signed main()
{
	cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>a[i];
	//输入
	for(int i=1;i<=n;i++)
	{
		f_ans[i]=f_ans[i-1];//统计方案前缀和
		id[i]=++tot;//赋予编号
		if(a[i]<=0) continue;
		//这个点不能作为区间 [x,r] 的右端点(a[r]<=0)
		//继续循环
		
		int p=i-1,nw=a[i];
		//枚举区间左端点 p,nw 记录区间 [p,r] 的和
		while(p>=1&&a[p]<=0&&nw+a[p]>0) nw+=a[p--];
		//左端点若还可以移动就移动
		for(int j=p+1,k=1;j<=i;j++,k++) f_ans[j]+=k,dis[j]+=k,id[j]=tot;
		//更新前缀和数组,dis,区间编号
		dis[i]=0;//不记录右端点为 i 的情况
		//dis 记录包含 i 的区间 且区间右端点不为 i 的区间的数量之和
	}
	
	while(q--)
	{
		int l,r;
		cin>>l>>r;
		if(id[l]==id[r]&&a[r]<=0) cout<<0<<'\n';
		//l,r 同属一个区间且 r 不能作为右端点,答案为 0
		
		else if(id[l]!=id[r]&&a[r]<=0) cout<<f_ans[r]-f_ans[l-1]-dis[r]<<'\n';
		//l,r 不属于同一个区间且 r 不可作为 r 所属区间的右端点,
		//因为前缀和 f_ans 记录了包含 r 的区间,但 r 不能作为 r 所属区间的右端点
		//所以前缀和相减后还要减掉 dis[r](可能有些难懂)
		
		else cout<<f_ans[r]-f_ans[l-1]<<'\n';
		//反之:
		//1: l,r 同属一个区间且 r 是右端点
		//2: l,r 不属于同一个区间且 r 可以作为 r 所属区间的右端点
		//前缀和直接相减即可
	}
	
	return 0;
}
posted @ 2025-02-10 11:11  Wy_x  阅读(19)  评论(0)    收藏  举报