202112-2 序列查询新解 c++ AC

O.题目

一、题解1——按照题目提示列表70分超时

可以看到复杂度为O(N),N最大可以是10e9,导致了超时。

#include<stdio.h>
#include<iostream>
#include<stdlib.h>
using namespace std;
int n,N;
long long A[100010],f[100010],g[100010];
int main()
{
	cin>>n>>N;
	for(int i=1;i<=n;i++)
	{
		cin>>A[i];
	}
	int p=0;
	int r=N/(n+1);
	long long error=0;
	for(long long i=0;i<N;i++)
	{
		f[i]=p;
		if(A[p+1]<=i+1&&p<n)p++;
		g[i]=i/r;
		error+=abs(f[i]-g[i]);
	}
	cout<<error;
}

如何减小时间复杂度?

将外循环缩减至O(n)也就是以A[i]为基准。开始观察数据:

发现一些规律

  • f[i] 和 g[i] 每次增1
  •  当i=A[j]时f [i]都会增1,也就是说f[ A[ j ] ] = f[ A[ j ] +1 ]=....f[ A[ j+1 ]-1 ]=j,直到 i=A[ j+1 ],f[ i ]才会增1
  • g[i] 从0每r个加1,g[i*r]=g[i*r+1]=.....g[i*(r+1)-1]

二、题解二 以不变应万变100分

题解参考序列查询新解-CSP202112-2_Crispo_W的博客-CSDN博客并加入自己的思考。

根据上述规律,整理思路

  1. gx是g的当前值,left指区间左端位置,gNext指g下一次加1时的位置,一般来说gNext+=r;
  2. 假设A[n+1]=N;不影响结果,免去特判
  3. A[]下标i从1到n+1遍历,下标取i时,在A[ i-1 ] ~ A[ i ]-1区间 f 的值不变,为 i-1。此时讨论g的值:
    1. A[i] <= gNext,也就是在区间内g的值gx相同,f的值i-1也相同。直接差值*区间长度即可。此时error += (ll)abs((i-1) - gx) * (ll)(A[i] - left);然后i++,f++继续区间左移。
    2. A[i] > gNext也就是在区间内g的值发生变化了,此时需要先算前面相同的部分,error += (ll)abs((i-1) - gx) * (ll)(gNext - left);这部分处理完了以后,区间位置移到未处理的位置,left = gNext; gNext += r;gx++;重复2,直到A[i] <= gNext,停止循环。

整体思路还是非常清晰简单的,不用求前缀和,也不需要数列求和,只需要抓住f和g变化的位置特征就行,只用计算|f-g|不变时的值*区间长度就行。

#include <iostream>
#include <cmath>
#define MAXN 100010 
using namespace std;
typedef long long ll;
int n, N;
int A[MAXN];
int main() {
    cin>>n>>N;
    A[0] = 0;
    A[n+1] = N; // 省去特判
    for (int i = 1; i <= n; i++) cin>>A[i];
    int r = N / (n+1);
    int gx = 0;
    //g值第一次变化下标是r,g每r次变化一下 
    int gNext = r;
    ll error = 0;
    for (int i = 1; i <= n+1; i++) {
        int left = A[i-1]; // 当前区间左端点值
        while (true) {
        	//f不变的区间内 g发生变化吗 
            if (A[i] <= gNext) {
            	//f=i-1,g=gx,区间长度cnt= A[i] - left
                error += (ll)abs((i-1) - gx) * (ll)(A[i] - left);
                break;
            }
            else {
            	//g不变的区间|f-g|值固定 
                error += (ll)abs((i-1) - gx) * (ll)(gNext - left);
                //区间移动 ,g值+1 
                left = gNext;
                gNext += r;
                gx++;
            }
        }
    }
    printf("%lld", error);
    return 0;
}

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值