【CSP】202112-2 序列查询新解
题目大意:
给定一长度为n+1的严格单增数列A[a0,a1,a2,a3...,an],其中a0=0,an<N
定义f(x)为数列A中小于等于x的最大整数的下标,r=floor(N/(n+1)),g(x)=floor(x/r)。
当N<1e9,n<1e4的时候,求解|g(x)-f(x)|之和,x=0,1,2...,N-1
分析:
数据规模较大,如果一项一项求和将会超时。
为优化朴素方法,观察求和式,发现由于g(x)向下取整的性质,在数轴上呈现为长度为r的阶梯上升状;同样由于f(x)的性质,在数轴上呈现为每个数列元素处上升1的阶梯状。
同时可以发现,如果按照数列元素而不是每个整数求和,复杂度将会大大降低。
结合以上两点,可以分析出f(x)和g(x)可以分别单独在区间上直接计算,并且a_x~a_x+1之间函数g(x)-f(x)存在至多一个零点,可以以此性质在同号区间内去掉绝对值符号,直接计算f(x)和g(x)的区间值。
代码:
先写出任意两点间计算g(x)之和的函数,再写出如果存在零点如何分割区间的函数,最终获得结果。
#include <cstdio> #include <iostream> #include <algorithm> #define up(l,r,i) for(int i=l;i<=r;i++) #define dn(l,r,i) for(int i=r;i>=l;i--) typedef long long ll; using namespace std; inline ll _max(const ll& a,const ll& b){return a>b?a:b;} inline ll _min(const ll& a,const ll& b){return a<b?a:b;} inline ll _abs(const ll& a){return a>0?a:-a;} ll n,N; ll r; ll solve(ll l,ll r,ll R){ //cout<<"计算"<<l<<","<<r<<"的总和"<<endl; ll sum = 0; ll lh,rh; lh = l/R;//左侧的阶梯状高度 rh = r/R;//右侧的阶梯状高度 if(lh == rh){ sum = (r-l+1)*lh; } else{ ll lleft = (l/R+1)*R - l; ll rleft = r%R+1; ll num = (r/R) - (l/R+1); //printf("在区间[%d,%d]左侧剩余%d,右侧剩余%d,中间有%d\n",l,r,lleft,rleft,num); sum = R*num*(lh+1+rh-1)/2 + lleft*lh + rleft*rh; } //cout<<"为"<<sum<<endl; return sum; } int main() { cin>>n>>N; r = N/(n+1); ll ans = 0; ll a,lpos = 0; up(1,n+1,i){ if(i != n+1) cin>>a; else a = N; a--; ll lh,rh; lh = lpos/r;//左侧的阶梯状高度 rh = a/r;//右侧的阶梯状高度 if((i-1-lh)*(i-1-rh) >= 0){ //printf("与之对应的%d\n",(i-1)*(a-lpos+1)); ans += _abs((i-1)*(a-lpos+1) - solve(lpos,a,r)); } else{ ll mid = (i-1)*r; //cout<<"选出中点为"<<mid<<endl; ans += _abs((i-1)*(mid-lpos+1) - solve(lpos,mid,r)); ans += _abs((i-1)*(a-mid) - solve(mid+1,a,r)); } lpos = a+1; } //printf("与之对应的%d\n",(N-lpos)*n); //ans += _abs((N-lpos)*n - solve(lpos,N-1,r)); cout<<ans<<endl; return 0; }