Educational Codeforces Round 145(div2) C. Sum on Subarrays

题目


 这一题的处理方法非常多,由于原题的数据特殊:n<=30 & | a |<=1000 。所以可以采用特殊的构造,比如用-1000(最大的负数)填补空缺,我们只需要找到满足k个正区间的构造法。

//可以处理更大 n 更大 a 的情况 而不受该题的数据范围的限制  
#include<bits/stdc++.h> 
using namespace std;
int t;
void solve()
{
	int n,k;
	cin>>n>>k;
	int a[n+1];
	memset(a,0,sizeof(a));
	int x=0;
	while((x+1)*(x+2)/2<=k){
		x++;
	} 
	for(int i=0;i<n;i++){
		if(i<x){
			a[i]=2;
		}
		else if(i==x){
			//a[i]=-2*x-1+2*(k-x*(x-1)/2);
			a[i]=(-2)*(x-(k-x*(x-1)/2))-1;
			//2 2 2 -3 -1000 -1000
		}
		else{
			a[i]=-1000;
		}
	}
	for(int i=1;i<=n;i++) cout<<a[i]<<" ";
	cout<<endl; 
}
int main()
{
	cin>>t;
	while(t--) solve();
	return 0;
 } 

 其实这一思路大多是不断寻找规律想出的,但是受到了数据的限制。


还有另一个更加通用的解法:双指针+贪心

先引用一个群里大佬的提醒:

C题可以让以i为右端点的满足条件的区间为i个(令a_i+前面的所有值>0),或者让以i为右端点到区间满足条件的区间为0个(令a_i+前面所有值<0)
可以证明这样的话,a_i最大只有n^2,而且可以构造出所有情况

 所以说,我们可以认为的控制第 i 号位置的数字,使其只有可能出现俩种情况:

(1)以 i 为右端点的区间,即【i-1,i】、【i-2,i】·······都是总和正数区间,即构造出 i 个正数区间。

(2)以 i 为右端点的区间,即【i-1,i】、【i-2,i】·······都是总和负数区间,即构造出0个正数区间。

有着俩种情况后,我们就可以开始构造数组了。

首先,我们要选出哪些位置需要我们满足情况(1),哪些满足情况(2)。很容易想到,在1~n之间选数,使和为k,最简单的方式就是贪心。贪心就很容易,比如n=6,k=8,就贪2和6。

我们把贪心的结果存起来(这里贪心的结果不会超过30,所以我设定了一个桶,存放哪些数是我们贪心得出来的,这样查找起来比较快)。

之后就是双指针的操作了:

解释起来比较复杂,建议画几个图体会。

还有就是俩指针分别是:

正指针:指向最后一个在负数前面的正数的位置。

负指针:指向最后一个在正数前面的负数的位置。

这里提供俩组足够测试下,想思路的测试数据:

输入:
6 8

输出:

-1 2 -3 -1 -1 6

输入:
9 23

输出:

-1 -1 -1 -1 -1 6 -7 8 1

//可以处理更大 n 更大 a 的情况 而不受该题的数据范围的限制  
#include<bits/stdc++.h> 
using namespace std;
int t;
void solve()
{
	int n,k;
	cin>>n>>k;
	int b[31];//桶  
	memset(b,0,sizeof(b));
	int t=k;
	for(int i=n;i>=0;i--){
		if(i<=t){
			b[i]++;
			t-=i;
		}
		if(t==0) break;
	}
	int last_p=0,last_n=0;//正数指针&负数指针  双指针  
	int a[n+1];
	memset(a,0,sizeof(a));
	for(int i=1;i<=n;i++){
		//i 就是 now 指针 
		if(b[i]){
			//放正数  查看负指针  
			if(i==1){
				a[i]=1;
				continue;
			}
			if(b[i-1]) a[i]=1;
			else{
				int sum=0;
				for(int j=last_n;j<i;j++){
					sum+=abs(a[j]);
				}
				a[i]=sum+1;
				//更新正指针  
				last_p=i;
				//cout<<"last + :"<<last_p<<endl;
			}
		}
		else{
			//放负数  查看正指针 
			if(i==1){
				a[i]=-1;
				continue;
			}
			if(!b[i-1]) a[i]=-1;
			else{
				int sum=0;
				for(int j=last_p;j<i;j++){
					sum-=a[j];
				}
				a[i]=sum-1;
				//更新负指针 
				last_n=i;
				//cout<<"last - :"<<last_n<<endl;
			}
		}
	}
	for(int i=1;i<=n;i++) cout<<a[i]<<" ";
	cout<<endl; 
}
int main()
{
	cin>>t;
	while(t--) solve();
	return 0;
 } 

再提一嘴:

群里另一个大佬的方法:前缀和

从后往前枚举右端点,维护前缀和,子段和分为正和负,负的话且当前数量+i还不够,就把ai变成-1000,如果够的话,就看插到第k个前缀后,接下来前面所有的都构造为i+1
维护前缀和i,即(i+3)×(i)/2

虽然但是,我还是不会用前缀和 维护。 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Awars_zpp

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值