这一题的处理方法非常多,由于原题的数据特殊: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
虽然但是,我还是不会用前缀和 维护。