CCF考试系统传送门
CCF-CSP计算机认证考试 202112-2 序列查询新解
CCF计算机软件能力认证考试系统
前言
最近刷题的时候遇到了这题,感觉逐步推理最终得出正解的过程挺不错的。现在来分享一下我的分析过程。
序列查询新解
1.题目内容
2.题解分析
一开始想用循环+二分的,复杂度为O(NlogN),而N是 1 0 9 10^9 109,肯定会超时。于是我们着眼于n,规模为 1 0 5 10^5 105,可以允许O(nlogn)左右的复杂度。这也就意味着,我们需要对A数组按区间进行处理。
接下来,寻找一个突破口。在前一题202112-1序列查询中,我是这样计算f(x)的和的:
a[n+1]=N;
for(int i=0;i<=n;i++)
ans+=i*(a[i+1]-a[i]);
区间[a[i],a[i+1]-1]内,f(x)=i,所以尝试对区间内进行处理。观察题目中样例解释部分,f(x)和g(x)的分布是不同的,为保证各部分|f(x)-g(x)|等于总和,意味着我们需要对区间进行划分。观察g(x)具有以下规律:按相同值对g(x)划分区间,则g(x)中区间是连续的且每个区间长度为r,区间之间的值按1递增。为什么是1?首先g(x)= ⌊ x r ⌋ \lfloor{\frac{x}{r}}\rfloor ⌊rx⌋,向下取整,其次r的最小值为1。
上述特性表示为:
对 x ∈ [ m r , ( m + 1 ) r − 1 ] , g ( x ) = m = ⌊ x r ⌋ 对x\in[mr,(m+1)r-1],g(x)=m=\lfloor{\frac{x}{r}}\rfloor 对x∈[mr,(m+1)r−1],g(x)=m=⌊rx⌋
该特性的作用在于当我们知道了x,就可以求出x所在区间的左端或者右端点。因此我们可以在区间[a[i],a[i+1]-1]内按g(x)的值进行再次划分,求各子区间的|f(x)-g(x)|,再分别求和。
如何分割区间?对于每次for循环[a[i],a[i+1]-1],我们可以视为此时 x ∈ [ a [ i ] , a [ i + 1 ] − 1 ] x\in[a[i],a[i+1]-1] x∈[a[i],a[i+1]−1],而f(x)=i,先设置一个值start存储左端点a[i],利用g(a[i])算出x在第一个子区间的函数值v,然后代入上面推的公式,(v+1)r-1即为第一个子区间的右端end,为防止end超过区间范围,使end=min((v+1)r-1,a[i+1]-1),所以(end-start)+1为子区间长度,v-i为子区间的值。所以对每个子区间有
∣ g ( x ) − f ( x ) ∣ = ∣ [ ( e n d − s t a r t ) + 1 ] ∗ ( v − i ) ∣ |g(x)-f(x)| = | [(end-start)+1]*(v-i) | ∣g(x)−f(x)∣=∣[(end−start)+1]∗(v−i)∣
对这个公式累加即可。为了能连续划分,需要使用while循环,每次循环开始前,使start=end+1,end到了右端点则跳出。
ll start,end=a[i]-1;
while(end<a[i+1]-1)
{
start=end+1;
ll v=start/r;
end=min((v+1)*r-1,a[i+1]-1);
ans+=abs((((end-start)+1)*(v-i)));
}
另外,变量类型需要使用long long,第一次提交因为没注意只拿了70分,改了就过了。
3.完整代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX_N=1e5+10;
ll n,N,ans;
ll a[MAX_N];
int main()
{
scanf("%lld%lld",&n,&N);
ll r=N/(n+1);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
a[n+1]=N;
for(int i=0;i<=n;i++)
{
ll start,end=a[i]-1;
while(end<a[i+1]-1)
{
start=end+1;
ll v=start/r;
end=min((v+1)*r-1,a[i+1]-1);
ans+=abs((((end-start)+1)*(v-i)));
}
}
printf("%lld",ans);
return 0;
}
评测结果