CF1398C Good Subarrays(写给我们萌新团体)

时间:2024-02-22 19:00:49

Good Subarrays

传送门:

Good Subarrays - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路

暴力!!!!!

一如既往的暴力!!!

复杂度O(n^2) 数据n到1e5

TLE必定TLE

我们可以用一个桶来优化

实质上其实还是高中所学的排列组合思想

第一步:当然是前缀和了,这边讲给新手写一下,有点冗杂,是高手直接跳到文章结尾点个赞就可以走了

rep(i,1,n){
	sum[i]=sum[i-1]+a[i];
}
//有很多题解里可能出现rep等字样来代替for
//这边一般是用宏定义的方法,在本题中rep(i,1,n)表示范围为[1,n];
//就是sum[i-1]表示1~i-1的和,那么sum[i]=sum[i-1]+a[i]就表示前i-1的和加上第i个数就是1~i的和

ok!!!最基础的解决了

第二步:变形求解

sum[j]-sum[i]=j-i;
题目无非要求的就是这种情况的组成方案数
简单解析一下这是什么:
sum[j],sum[i]分别表示第前j个数的和和前i个数的和(这里令j>i,为了求i+1~j的和)
那么sum[j]-sum[i]就是i+1~j的和,j-i就是这个i+1~j这个区间有多少数
题目要求的不就是正好 区间i+1~j的和=区间里包含是数有几个
这边用i+1只是为了后面方便就计算
其实这变成i~j后面变成j-i+1也是一样的要有i=i+1这样的编程转换思想,要是不懂用x=i+1来套这里就不过多讲述

ok!?我们知道要求什么了
但是这样的条件是涉及到两个数的,涉及到两个数那只能是暴力判断符合条件然后一个一个加过去
我们这边就需要把他优化变成只涉及到当前数也就是一个数那边是不是可以优化到O(n)了

仔细一想变形成sum[j]-j=sum[i]-i是不是就至于i(说跟j相关也没关系跟上面所描述的变成思想是一回事)相关了

第三步:桶优化

这边用map来代替桶,为什么呢?
因为用数组可能越界
比例说我当前的数据为000000……
那是不是马上就可以知道前i个数之和减去i肯定小于0
用map虽然会稍微慢一点(因为map的查找是O(logn))但是这道题够用
基本够用的O(logn),因为O(logn)最多也才几十我感觉跟O(1)差不多的

开头我们说了这里和高中的排列组合有关系,就是在这
为了方便思考和方便写这边就不写sum[j]-j,sum[i]-i之类的我直接用第k个数表述(就是本小块中的第k个数就是表述sum[k]-k,第一个数就表示sum[1]-1,……以此类推,这句话有点扯淡,写到后面我发现好像这样跟繁琐了)
那么我们遇到一个从未遇到的情况那是不是没有别的情况与当前情况匹配,那么这时候当前方案数就是0
如果我们第二次遇到这种情况那么当前方案数就是1(因为符合了sum[j]-j=sum[i]-i也就是符合了sum[j]-sum[i]=j-i,这边i肯定不等于j的我就不加以赘述了)
那么以此类推我第三次遇到这种情况当前方案数就是2
……
但是有跟我一样的萌新可能就不懂了
第二次遇到肯定是好理解的,那么第三次遇到呢?
这边我们假设也不用假设后面遇到的k肯定是大于前面的
那么组成情况就有两种,第一次是跟第一次数遇到的组,第二次跟第二次遇到的数组
那是不是2种,
这边只需要考虑当前情况做j,然后把已有的情况做i(j是既定的,i是之前遇到的)

代码如下

	rep(i,1,n) {//与上述rep一样
		//a[i]=nums[i-1]-'0';
		//sum[i]=sum[i-1]+a[i];
		res+=m[sum[i]-i]++;这边其实求完m之后做一个0~n-1的等差数列也ok的意思一样的要是这边不太好理解
		//后置++是先放参与加运算再自身+1
	}

ok了思路就是这么多

AC code

// Problem: 
//     Good Subarrays
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF1398C
// Memory Limit: 250 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include<iostream>
#include<algorithm>
//#include<cstdio>
#include<map>
#define ll long long 
#define int long long
#define endl '\n'
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>(b);i--)
#define N 100010 //1e6+100 
 
using namespace std;
int a[N],n,t,sum[N];
//本题需要用long long为了图方便我就直接宏定义把int定义成long long了
string nums;
map<int,int>m;
void solve(){
	m.clear();
	int res=0;
	m[0]=1;//这句话一定要打!
	//这是为什么呢?因为如果当前前缀和(表示为1~i个数的和)等于这段区间长度i那么测试就是sum[i]-i=0那就不用去找别的数了肯定是好子数组
	//这里其实也是一个比较难理解的点
	//这样理解可能比较好动m[0]=1就表示m[sum[0]-0]=1也就是说有符合sum[0]-0==sum[i]-i这边(i仅表示第一次遇到,i=0的时候是他第0次遇到,所以当第一次遇到之前,他们已经相遇过一次,方法就按上面的方法继续处理下去)
	//这是个重难点,需要思考一下
	cin>>n>>nums;
	rep(i,1,n) {
		a[i]=nums[i-1]-'0';
		sum[i]=sum[i-1]+a[i];
		res+=m[sum[i]-i]++;
	}

    cout<<res<<endl;
}
signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>t;
	while(t--) solve();
	return 0; 
} 

有问题请评论或私信我,谢谢!

有共同学习需求的可以加入洛谷团队:https://www.luogu.com.cn/team/66731