【CCF-CSP】202112-2-序列查询新解100分(读过必懂)

故事的开头总是极尽温柔,故事会一直温柔……💜

✨你好啊,我是“ 怪& ”,是一名在校大学生哦。
🌍主页链接:怪&的个人博客主页
☀️博文主更方向为:课程学习知识、作业题解、期末备考。随着专业的深入会越来越广哦…一起期待。
❤️一个“不想让我曾没有做好的也成为你的遗憾”的博主。
💪很高兴与你相遇,一起加油!

一、🌳代码如下:

#include <iostream>
#include <cmath> //绝对值函数abs()头文件
using namespace std;
#define num 100002

int n=0;//序列有n个数 
int N=0;//N的值 
int a[num]={0};//序列最多有1*10^5个数 
int  r=0;// r=N/(n+1) 
int Long=0; 
long long sum=0;

void input(){//输入 
	cin>>n>>N;
	for(int i=1;i<=n;i++){
		cin>>a[i]; 
	} 
} 


long long g(int x){//计算g(x)的值 
	return x/r;	
} 

int main(){
	input();
	r=N/(n+1);	//cout<<r<<endl; //检验r的计算 
	a[n+1]=N;
	a[0]=0;
	//Long=r;
	for(int i=1;i<=n+1;i++){//以f(i)为区域划分计算 
		long long sum1=0;//记录此小区间差值的和 
		for(int j=a[i-1];j<=a[i]-1;j=j+Long){//此区间内有Long个g取值为g(j)的数 
			int NumEnd=(g(j)+1)*r-1;//g取值为g(j)最大的数为NumEnd
			if(NumEnd>a[i]-1) NumEnd=a[i]-1;//上界超出范围,变为区间最上界 
			int NumLong=NumEnd-j+1;//取值为g(j)的数为NumLong个
			long long f_g=abs(i-1-g(j));//f与g差值,f值恒为i-1 
			sum1=sum1+f_g*NumLong;
			Long=NumLong;
		}
		sum=sum+sum1;			
	}
	
	cout<<sum<<endl;
	return 0;
} 

二、🌵解题思路

1、观察性质🍠

f(x)定义:序列A中小于等于x的整数里的最大的数的下标。
g(x)计算方式:r=N/(n+1)   g(x)=x/r
可得f(x)、g(x)分布皆为公差为1的递增序列
示例给的图很直观的表现了此规律:
请添加图片描述

2、求解步骤🍅

(1)、题目的暗示

CSP的第一、二题联系紧密,这次的第一题中已经给出了暗示:分段!
如果忘了第一题讲是什么?可以看我的这篇文章:【CCF-CSP】202112-1-序列查询100分
其中详细分析了题意、解题思路并附上了题目截图。

(2)、如何分段

🍀先对f分段:for(int i=1;i<=n+1;i++) //以f(i)为区域划分计算
在此区域内f的取值相同,值为:i-1
🌍再对每个f值相同的区域按照g值进行分段:for(int j=a[i-1];j<=a[i]-1;j=j+Long){//此区间内有Long个g取值为g(j)的数
🔥j值改变条件为:j=j+Long,这个Long为变值,因为g取值相同的区间不完全与f取值相同的区间对齐
所以每次都要计算此Long:Long=NumLong=NumEnd-j+1;
注:由于此特性,所以g取值的上界可能超出了此区间(按f取值相同来划分的区间)的上界
所以需要此语句:if(NumEnd>a[i]-1) NumEnd=a[i]-1;//上界超出范围,变为区间最上界 ,来将此情况的bug修复。

(3)、求和

上述操作先按f取值相同分段,再按g值相同分段,所以在最底层的段中f值与g值皆恒定,即两者的差值也恒定:以差值乘以数量即可,最后将所有区间计算量加起来即可。

3、提交结果

请添加图片描述

三、其他思路

再附上一个代码,案例能对,但是0分,数据太大爆了,有兴趣的可以看看思路。

#include <iostream>

using namespace std;
#define num 100002

int n=0;//序列有n个数 
int N=0;//N的值 
int long a[num]={0};//序列最多有1*10^5个数 
int long r=0;// r=N/(n+1) 
long long sum=0;

void input(){//输入 
	cin>>n>>N;
	for(int i=1;i<=n;i++){
		cin>>a[i]; 
	} 
} 

void output(){//输入,检验输入是否正确 
	cout<<n<<" "<<N<<endl;
	a[0]=0;
	for(int i=1;i<=n;i++){
		cout<<a[i]<<" ";
	}	
	cout<<endl;
} 


long long g(int x){//计算g(x)的值 
	return x/r;	
} 

long long	GSum(long long x,long long  y){//求x到y间,g(i)的和 
	long long gsum=0;
	if(g(x)==g(y)){
		gsum=g(x)*(y-x+1);//相同的g()值共y-x+1个 
	}
	else{
		long long xTop=(x%r)*g(x);//此区间内不包括前(x%r)个g(x) 
		long long yTop=(y%r+1)*g(y);//此区间内包含(y%r+1)个g(y);
		//r组相同的等差数列,首项为g(x),末项为g(y)-1,共 g(y)-1-g(x)+1项 
		gsum=r*((g(x)+g(y)-1)/2*(g(y)-1-g(x)+1)); 
		gsum=gsum-xTop+yTop;//修正sum,多加的xTop需减去,少加的yTop需加上	
	}
	return gsum; 
}


int main(){
	input();
	r=N/(n+1);	//cout<<r<<endl; //检验r的计算 
	a[n+1]=N;
	for(int i=1;i<=n+1;i++){//以f(i)为区域划分计算 
		
		long long fFlag=i-1;//记录此区间的f(i)值 
		long long LeftFlag=a[i-1]; //区间左边界 
		long long RightFlag=a[i]-1;//区间右边界 
		long long LongFlag=RightFlag-LeftFlag+1;//区间长度 
		//i在LeftFlag与RightFlag中,f(i)取值相同
		
		if(LongFlag==1){//区间仅一个长度 
			long long m1=fFlag-g(LeftFlag);
			if(m1<0) m1=(-1)*m1;
			sum=sum+m1;
		} 
		else if(g(LeftFlag)>=fFlag){//在此区间内,g(i)恒大于等于f(i),f(i)恒=i-1 
			sum=sum+GSum(LeftFlag,RightFlag)-fFlag*LongFlag;
		}
		else if(g(RightFlag)<=fFlag){//在此区间内,g(i)恒小于等于f(i),f(i)恒=i-1 
			sum=sum+fFlag*LongFlag-GSum(LeftFlag,RightFlag);
		} 
		else{//在此区间内,g(i)先<=f(i),后>f(i) 
			int WhereFlag=0;//记录g(i)>f(i)的左临界
			for(int j=LeftFlag;j<=RightFlag;j++){
				if(g(j)>fFlag){
					WhereFlag=j;
					break;//跳出 
				}
			}
			long long LeftLong=WhereFlag-LeftFlag;//g(i)<=f(i)部分长度 
			long long RightLong=RightFlag-WhereFlag+1;//g(i)>f(i)部分长度 
			long long sum1=fFlag*LeftLong-GSum(LeftFlag,WhereFlag-1);
			long long sum2= GSum(WhereFlag,RightFlag)-fFlag*RightLong;
			sum=sum+sum1+sum2; 
		}
		//cout<<sum<<endl;
	}
	cout<<sum<<endl;
	return 0;
} 

四、题目如下:

请添加图片描述

  • 36
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

怪&

感谢您的支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值