1 条题解

  • 3
    @ 2023-11-13 11:18:03

    此题做法与回到过去2的主要差别在于加入了递归的思想。若还未完全理解二分的写法请跳转回到过去2的题解,先充分理解 wananwanan giegie讲的二分的思想再继续。

    本题直接二分查找的话由于加入了负数,故不能保证后面的区间都满足条件。不妨先取区间中间值:

    区间

    由此把子区间 [l,r][ l, r ] 分为以下三类:

    1. LlrMidL \leq l \leq r \leq Mid

      区间1
    2. Mid<lrRMid < l \leq r \leq R

      区间2
    3. LlMid,Mid<rRL \leq l \leq Mid, Mid < r \leq R

      区间3

    先忽略1、2类区间,重点考虑如何快速解决第三类区间。

    预先求出前缀和,方便区间求和:

    scanf("%d %lld", &n, &k);
    for(int i = 1; i <= n; i++)
    {
    	scanf("%lld", &a[i]);
    	h[i] = h[i - 1] + a[i];//预处理前缀和
    }
    

    将第三类区间以 MidMid 为界分成左右两个区间,此时只需将所有的右区间全部求和后排序:

    int cth = 0;
    for(int i = mid + 1; i <= r; i++)
    	rh[cth++] = h[i] - h[mid];//记录右半部分和
    sort(rh, rh + cth);//排序
    

    再遍历左区间起点 ll ,每次经过一左区间时先计算出左区间和,由此得出欲满足条件则右侧区间最小的和,在记录右侧区间和的数组中二分查询即可求出起点为 ll 且满足条件的区间个数。第三类区间解决。

    for(int i = l - 1; i < mid; i++)
    {
    	long long lh = h[mid] - h[i];//左半部分和
    	long long fd = k - lh;//满足条件的右侧部分最小和
    	int l1 = 0, r1 = cth - 1;
    	if(rh[r1] >= fd)//确保存在大于fd的元素
    	{
    		while(l1 < r1)//二分查找第一个大于fd的元素下标
    		{
    			int miid = (l1 + r1) / 2;
    			if(rh[miid] >= fd)
    				r1 = miid;
    			else
    				l1 = miid + 1;
    		}
    		ans += cth - l1;//满足条件的区间个数
    	}
    }
    

    余下的第1、2类区间可以通过递归的方式解决 递归


    完整代码

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    using namespace std;
    
    int n;
    long long k, ans;
    long long a[200010];//原数组
    long long h[200010];//前缀和数组
    long long rh[200010];//记录数组
    
    void solve(int l, int r)
    {
    	if(l == r)//单点判断后返回
    	{
    		if(a[l] >= k)
    			ans++;
    		return;
    	}
    	int mid = (l + r) / 2;
    	solve(l, mid);//递归解决左侧区间
    	solve(mid + 1, r);//递归解决右侧区间
    	int cth = 0;
    	for(int i = mid + 1; i <= r; i++)
    		rh[cth++] = h[i] - h[mid];//记录右半部分和
    	sort(rh, rh + cth);//先排序后二分
    	for(int i = l - 1; i < mid; i++)
    	{
    		long long lh = h[mid] - h[i];//左半部分和
    		long long fd = k - lh;//满足条件的右侧部分最小和
    		int l1 = 0, r1 = cth - 1;
    		if(rh[r1] >= fd)//确保存在大于fd的元素
    		{
    			while(l1 < r1)//二分查找第一个大于fd的元素下标
    			{
    				int miid = (l1 + r1) / 2;
    				if(rh[miid] >= fd)
    					r1 = miid;
    				else
    					l1 = miid + 1;
    			}
    			ans += cth - l1;//满足条件的区间个数
    		}
    	}
    }
    
    int main()
    {
    	ans = 0;//初始化ans
    	scanf("%d %lld", &n, &k);
    	for(int i = 1; i <= n; i++)
    	{
    		scanf("%lld", &a[i]);
    		h[i] = h[i - 1] + a[i];//预处理前缀和
    	}
    	solve(1, n);//从整个区间开始
    	printf("%lld", ans);
    	return 0;
    }
    
    • @ 2023-11-13 19:12:31

      orz tql 4eyebird我滴神

    • @ 2023-11-13 19:13:41

      @ wanangiegie还得是你orz orz

    • @ 2023-11-13 19:14:08

      @ 写了这个题能回到过去吗

    • @ 2023-11-13 19:17:35

      @谁知道呢,要不然再来一次?👀️

  • 1

信息

ID
927
时间
1000ms
内存
256MiB
难度
9
标签
递交数
31
已通过
3
上传者