【2020ccpc长春站】L题 Coordinate Paper(构造)详解

题目链接:https://codeforces.com/gym/102832/problem/L

题目大意:

构造一个满足下面条件的长度为n的序列:

①序列中的任意一个数a_i都是非负整数

②序列中所有数之和为s     \sum_{i=1}^{n}a_i=s

③相邻的两个数a_i,a_{i+1}满足a_{i+1}=a_i+1a_{i+1}=a_i-k

其中n,s,k都由题目给出(1≤n,k≤10^5,1≤s≤10^{18}

分析:

第一步:

我先画了这样一个图(先不考虑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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值