题目链接:https://codeforces.com/gym/102832/problem/L
题目大意:
构造一个满足下面条件的长度为n的序列:
①序列中的任意一个数都是非负整数
②序列中所有数之和为s
③相邻的两个数满足或
其中n,s,k都由题目给出(1≤n,k≤,1≤s≤)
分析:
第一步:
我先画了这样一个图(先不考虑ai-k会小于0)
有点丑QAQ
a是这个序列的第一个元素
可以很轻易的发现,在所有方案下(即不管后面的元素是+1还是-k),序列所有元素的和都似乎满足一个规律:
4a+ x- (6-x)k
可以发现前面的是4a都是一样的,后面的部分是从6开始不断减去(1+k)
换句话说这些数它们对(k+1)取模的值是相等的
也就是说,如果序列的第一个数a已经确定了,那这个序列所有数之和模(k+1)的值也是确定的
这个规律是可以一般化的,可以这么理解:
不考虑a的影响(因为所有方案下,序列中都包含相同个数的a),那么我们每一步要不就对序列和+1,要不就对序列和-k,而-k和+1在模(k+1)的意义下是等价的操作
这个规律是很重要的,因为我们要构造的序列需要满足的第二个条件就是所有数之和为s,那么s%(k+1)就是一个给定的固定值.
而对于一个给定的a,序列中所有数之和模(k+1)的值也是可以确定的,如果这个值不等于s%(k+1),那么显然a不合法
综上,这道题解法的第一步就已经得到了:
枚举序列的第一个元素a在模(k+1)下的值,寻找合法的a
为什么枚举模(k+1)的值?
因为如果a=1是合法的,则a+k+1,a+2k+2......必然也合法
第二步:
通过第一步,已经得到了序列的第一个数了,而序列后面的数都可以由序列的第一个数递推得到.
问题是怎么推呢?因为每次都有-k和+1两种方案,怎么才能得到合法的序列呢?
这么想:
我们已经得到了a,假设序列所有数之和值为x.
由第一步已知,x%(k+1)==s%(k+1)
如果x等于s,那最好了,直接得到了答案
而如果x小于s,那么我们只要不断让这个序列中的数加上(k+1)就能让x逼近s直到等于s
而如果x大于s,那么我们只要不断让这个序列中的数减上(k+1)也能让x逼近s直到等于s
问题是我们要构造的序列需要满足每个数都是非负整数,也就是说对于给定的a,构造出来的序列是有最小值的,至于最大值显然是正无穷
那么思路就出来了:
由给定的a,推出由a构造出的值最小的序列,然后不断加上k+1直到等于s
怎么得到值最小的序列?
每次都有+1和-k两种操作,只要让-k发生的尽可能多就可以了
通过第一步得到了序列的第一个数模(k+1)下的值a,我们现在要让序列和最小,所以让序列第一个元素直接等于a
第二个元素等于a+1
第三个元素等于a+2
......
不断++
因为要让-k尽可能多,所以如果某个元素可以-k了,直接让它变为0
也就是说这个序列为a,a+1,a+2,......k,0,1,2,3 ...... k,0,1,2,3.....
这个序列显然满足题目要求的条件①和条件③,唯一差的就是序列所有元素值之和小于s
另外,如果连这个序列所有数之和都大于s了,那a显然也是不合法的
第三步:
现在已经得到了一个序列了,但是这个序列的所有数之和是小于等于s的,所以我们需要让它不断加上k+1
问题是怎么在让序列依然合法的情况下加上k+1?
我的思路:
让每个元素都加上k+1显然这个序列依然是合法的,因为元素间的相对关系仍然满足条件③
所以先尽量让这个操作实现
设第二步构造出的序列,其所有元素值之和为x,序列长度为n
则num=(s-x)/(k+1)就是我们需要给序列加上的k+1的个数
因为尽量让上面的操作实现,所以先让每个元素加上num / n个(k+1)
还剩下num % n(这个值显然小于n) 个(k+1)需要加上
假设序列如下:
a,a+1,a+2 ......k-1,k,0,1,2,3......,k-1 ,k,0,1,2,3......
我发现按照顺序让每个元素加上k+1貌似不会违背题目给的条件②
a,a+1,a+2 ......k-1,k,0,1,2,3......,k-1 ,k,0,1,2,3......
→a+k+1,a+1,a+2 ......k-1,k,0,1,2,3......,k-1 ,k,0,1,2,3......
→a+k+1,a+k+2,a+2 ......k-1,k,0,1,2,3......,k-1 ,k,0,1,2,3......
.............
→a+k+1,a+k+2,a+k+3 ......2k,k,0,1,2,3......,k-1 ,k,0,1,2,3......
→a+k+1,a+k+2,a+k+3 ......2k,2k+1,0,1,2,3......,k-1 ,k,0,1,2,3......
但是问题出现在了 k 到 0的这一段
如果让k加上k+1则它比0大了2k+1,所以序列不合法了
解决方法:
先让0加上k+1,再让k加上k+1
a+k+1,a+k+2,a+k+3 ......2k,k,k+1,1,2,3......,k-1 ,k,0,1,2,3......
→a+k+1,a+k+2,a+k+3 ......2k,2k+1,k+1,1,2,3......,k-1 ,k,0,1,2,3......
是不是就合法了呢
根据上述的想法,这道题就做完了
总结一下思路:
①发现给定的a(序列第一个元素)可以确定序列所有元素值之和模k+1的值,而这个值必须等于s模k+1
②寻找合法的a,并通过a构造出一个令序列所有元素值之和尽量小的序列
③让这个序列不断加上k+1逼近s
构造完成
AC代码:
寻找合法的a:
min_suma表示对于这个a能构造出的最小的序列所有元素值之和
可以用O(1)复杂度计算出这个min_suma
然后判断这个a合不合法:
两个条件:min_suma小于s 且 二者模k+1的值相等
如果找到了这个a,我们必定能构造出一个合法的序列
long long flag=0;
long long min_suma;
for(long long a=0;a<=k;a++){
if(n<k-a+1) min_suma=(a+a+n-1)*n/2;
else{
min_suma=(a+k)*(k-a+1)/2;
long long cnt=(n-(k-a+1))/(k+1);
long long remainder=(n-(k-a+1))%(k+1);
min_suma+=(cnt*k*(k+1)/2+(0+remainder-1)*remainder/2);
}
if(min_suma%(k+1)==s%(k+1)&&s>=min_suma){
flag=1;
ans[1]=a;
break;
}
}
如果找不到这个a,那么不存在这样合法的序列:
if(!flag) printf("-1\n");
构造序列的过程:
for(long long i=2;i<=n;i++)
ans[i]=(ans[i-1]+1)%(k+1);
long long t=(s-min_suma)/(k+1);//需要加上的k+1的个数
for(long long i=1;i<=n;i++)
ans[i]+=(t/n)*(k+1);
long long remain=t%n; //还需要加上的k+1的个数
long long p=1;
while(remain--){
if(ans[p]%(k+1)==k&&p!=n){ //如果遇到了k,那么先让后面的0加上k+1
ans[p+1]+=(k+1);
if(remain>0) ans[p]+=(k+1),remain--;
p+=2;
}
else
ans[p++]+=(k+1);
}
for(long long i=1;i<=n;i++)
i==1?printf("%lld",ans[i]):printf(" %lld",ans[i]);
printf("\n");
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
long long ans[100005];
int main()
{
long long n,k;
long long s;
scanf("%lld%lld%lld",&n,&k,&s);
long long flag=0;
long long min_suma;
for(long long a=0;a<=k;a++){
if(n<k-a+1) min_suma=(a+a+n-1)*n/2;
else{
min_suma=(a+k)*(k-a+1)/2;
long long cnt=(n-(k-a+1))/(k+1);
long long remainder=(n-(k-a+1))%(k+1);
min_suma+=(cnt*k*(k+1)/2+(0+remainder-1)*remainder/2);
}
if(min_suma%(k+1)==s%(k+1)&&s>=min_suma){
flag=1;
ans[1]=a;
break;
}
}
if(!flag) printf("-1\n");
else{
for(long long i=2;i<=n;i++)
ans[i]=(ans[i-1]+1)%(k+1);
long long t=(s-min_suma)/(k+1);
for(long long i=1;i<=n;i++)
ans[i]+=(t/n)*(k+1);
long long remain=t%n;
long long p=1;
while(remain--){
if(ans[p]%(k+1)==k&&p!=n){
ans[p+1]+=(k+1);
if(remain>0) ans[p]+=(k+1),remain--;
p+=2;
}
else
ans[p++]+=(k+1);
}
for(long long i=1;i<=n;i++)
i==1?printf("%lld",ans[i]):printf(" %lld",ans[i]);
printf("\n");
}
return 0;
}