2025.7.5 前缀和

前缀和

常用于区间求和,对于长度为 \(n\) 的连续区间求和,时间复杂度从 \(O(n)\) 降到 \(O(1)\)

实现

构造

for(int i=1;i<=n;i++){
    f[i]=f[i-1]+a[i];
}

查询

\(\sum_{i=l}^{r}a_i=f_r-f_{l-1}\)

int sum1=0,sum2=0;
for(int i=l;i<=r;i++){
    sum1+=a[i];
}
sum2=f[r]-f[l-1];
//sum1==sum2

例题P8865

题目描述

花园可以看作有 \(n*m\) 个位置的网格图,从上到下分别为第 \(1\) 到第 \(n\) 行,从左到右分别为第 \(1\) 列到第 \(m\) 列,其中每个位置有可能是土坑,也有可能不是,可以用 \(a_{ij}\) 表示第 \(i\) 行第 \(j\) 列这个位置有土坑,否则用 \(a_{ij}\) 表示这个位置没土坑。

一种种花方案被称为 \(C\) 型的,如果存在 \(x_1,x_2\in[1,n]\) ,以及 \(y_0,y_1,y_2\in[1,m]\) ,满足 \(x_1+1<x_2\) ,并且 \(y_0<y_1,y_2\leq{m}\) ,使得第 \(x_1\) 行的第 \(y_0\) 到第 \(y_1\) 列、第 \(x_2\) 行的第 \(y_0\) 到第 \(y_2\) 列以及第 \(y_0\) 列的第 \(x_1\) 到第 \(x_2\) 行都不为土坑,且只在上述这些位置上种花。

一种种花方案被称为 \(F\) 形的,如果存在 \(x_1,x_2,x_3\in[1,n]\) ,以及 \(y_0,y_1,y_2\in [1,m]\) ,满足 \(x_1+1<x_2<x_3\) ,并且 \(y_0<y_1,y_2\leq{m}\) ,使得第 \(x_1\) 行的第 \(y_0\) 到第 \(y_1\) 列、第 \(x_2\) 行的第 $y_0 $到第 \(y_2\) 列以及第 \(y_0\) 列的第 \(x_1\) 到第 \(x_3\) 行都不为土坑,且只在上述这些位置上种花。

现在小 C 想知道,给定 \(n,m\) 以及表示每个位置是否为土坑的值 \(a_{ij}\)\(C\) 形和 \(F\) 形种花方案分别有多少种可能?由于答案可能非常之大,你只需要输出其对 998244353 取模的结果即可,具体输出结果请看输出格式部分。

[!NOTE]

对于所有数据,保证:\(1\leq{T}\leq5\) , \(1\leq{n},m\leq10^3\)\(0\leq{c},f\leq1\)\(a_{ij}\in\{0,1\}\)

思路

首先考虑暴力:枚举同一列不同行且间隔行数至少为一的两个点,时间复杂度为 \(O(n^3)\) ;再横着分别枚举两个点右侧有几个连续不为“坑”的格数,时间复杂度再乘 \(O(n)\) 。即使用前缀和可以把右侧连续不为“坑”个数计算的时间复杂度降到 \(O(1)\) ,但 \(O(n^3)\) 的时间复杂度仍不可接受。

我们实际可接受的时间复杂度为 \(O(n^2)\) ,所以至少消掉一个 \(O(n)\)

对于每一个 \(C\) 型,我们并不关心之前的行数具体如何分布,我们只在意在两行之前横向的有几种选择方案,在乘上当前一行的方案数。

对于每一个 \(F\) 型,我们可以把它看作一个 \(C\) 型加上“竖”,所以只要知道在当前行之前有几个 \(C\) 型。

[!IMPORTANT]

如果竖着枚举时发现有“坑”,要把之前的选择方案清空!

AC代码

#include<bits/stdc++.h>
using namespace std;
const int N=1005,mod=998244353;
int t,id,n,m;
int h[N][N],l[N][N];
long long c,f,ansc,ansf;
string s;
bool a[N][N];
int main(){
	scanf("%d%d",&t,&id);
	while(t--){
		memset(h,0,sizeof(h));
		memset(l,0,sizeof(l));
		ansc=ansf=0;
		scanf("%d%d%lld%lld",&n,&m,&c,&f);
		for(int i=1;i<=n;i++){
			cin>>s;
			for(int j=1;j<=m;j++){
				a[i][j]=1-s[j-1]+'0';//个人习惯,“1”表示可以种花
			}
		}
		for(int i=n;i>=1;i--){
			for(int j=m;j>=1;j--){
				if(a[i][j]){//不为“坑”
					l[i][j]=max(i,l[i+1][j]);
					h[i][j]=max(j,h[i][j+1]);//奇特的"前缀和",h[i][j]表示第i行的j~h[i][j]都可种花
				}
				else{
					l[i][j]=h[i][j]=-1;//为“坑”
				}
			}
		}
		
		for(int j=1;j<m;j++){
			long long ll=0,p=0;//ll:之前的横向选择数,p:前i-1行的“C”型个数
			for(int i=1;i<=n;i++){
				if(!a[i][j]){
					ll=p=0;//清零
					continue;
				}
				long long x=h[i][j]-j;//当前行的选择方案数
				ansc+=(x*ll)%mod;
				ansc%=mod;
				ansf+=p%mod;
				ansf%=mod;
				p+=x*ll%mod;//累计“C”型个数
				p%=mod;
				ll+=max(0,h[i-1][j]-j);
			}
		}
		printf("%lld %lld\n",ansc*c,ansf*f);
	}
	return 0;
}

完结撒花!!!

posted @ 2025-07-06 10:45  liyuan2023  阅读(17)  评论(0)    收藏  举报