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博客并加入自己的思考。
根据上述规律,整理思路
- gx是g的当前值,left指区间左端位置,gNext指g下一次加1时的位置,一般来说gNext+=r;
- 假设A[n+1]=N;不影响结果,免去特判
- A[]下标i从1到n+1遍历,下标取i时,在A[ i-1 ] ~ A[ i ]-1区间 f 的值不变,为 i-1。此时讨论g的值:
- 当A[i] <= gNext,也就是在区间内g的值gx相同,f的值i-1也相同。直接差值*区间长度即可。此时error += (ll)abs((i-1) - gx) * (ll)(A[i] - left);然后i++,f++继续区间左移。
- 当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;
}