【训练题35:数学】D:石子游戏 | 2021牛客寒假算法基础集训营 5

【题意】D:石子游戏 | 2021牛客寒假算法基础集训营 5

  • 一排,有 n n n 个石头堆,第 i i i 堆有 a i a_i ai 个石头。
    给你一个 k k k
    你每次可以选择相邻的 k k k 个石头堆,进行一次操作,这 k k k 堆,每堆石头个数都会增加 1 1 1
    问你最少操作个数使得每堆石头个数都相同,或者输出 − 1 -1 1 表示不可能。

【范围】

  • 样例组数 T ≤ 10 T\le 10 T10
    1 ≤ k ≤ n ≤ 1 0 5 1\le k\le n\le 10^5 1kn105
    0 ≤ a i ≤ 1 0 9 0\le a_i\le 10^9 0ai109

【思路】赛内

  • 我感觉不是直接二分+贪心就能做好的,因为假设最后每堆石头个数都是 x x x
    可能 x x x 是可以达成的,但是 x − 1 x-1 x1 x + 1 x+1 x+1 都是无法达成的,比较棘手。
    于是我就对 x x x 进行了分析:
    例子: n = 5 , k = 3 , a [   ] = { 5   , 2   , 1   , 3   , 7 } n=5,k=3,a[\ ]=\{ 5\ ,2\ ,1\ ,3\ ,7\} n=5k=3a[ ]={5 ,2 ,1 ,3 ,7}
    我们已经假设最终每堆都有 x x x 个石头了,那么由于第一堆增加到 x x x 的方案只有一种,就是选择增加 1 , 2 , 3 1,2,3 1,2,3 堆,每堆增加 x − 5 x-5 x5 个,于是变成了:
    a [   ] = { x   , x − 3   , x − 4   , 3   , 7 } a[\ ]=\{ x\ ,x-3\ ,x-4\ ,3\ ,7\} a[ ]={x ,x3 ,x4 ,3 ,7}
    重复下去。第二堆石头需要增加 3 3 3 个,增加方案也只有一种,就是 2 , 3 , 4 2,3,4 2,3,4 堆每堆增加 3 3 3 个。
    我们从头到尾来一遍:
    a [   ] = { 5   , 2   , 1   , 3   , 7 } a [   ] = { x   , x − 3   , x − 4   , 3   , 7 } a [   ] = { x   , x   , x − 1   , 6   , 7 } a [   ] = { x   , x   , x   , 7   , 7 } \begin{matrix} a[\ ]&=\{&5\ ,&2\ ,&1\ ,&3\ ,&7\}\\ a[\ ]&=\{&x\ ,&x-3\ ,&x-4\ ,&3\ ,&7\}\\ a[\ ]&=\{&x\ ,&x\ ,&x-1\ ,&6\ ,&7\}\\ a[\ ]&=\{&x\ ,&x\ ,&x\ ,&7\ ,&7\}\\ \end{matrix} a[ ]a[ ]a[ ]a[ ]={={={={5 ,x ,x ,x ,2 ,x3 ,x ,x ,1 ,x4 ,x1 ,x ,3 ,3 ,6 ,7 ,7}7}7}7}
    可以看到,有解的情况就是 x = 7 x=7 x=7,即剩下下标从 n − k + 2 ∼ n n-k+2\sim n nk+2n 的所有数字都相同。
    当然还有一些特殊情况,比如:
    n = 3 , k = 3 , a [   ] = { 1 , 1 , 1 } n=3,k=3,a[\ ]=\{1,1,1\} n=3k=3a[ ]={1,1,1}
    这样我们操作完之后, a [   ] = { x , x , x } a[\ ]=\{x,x,x\} a[ ]={x,x,x}。这个时候,我们的解就是原来序列的元素的最大值
    我们得到了 x x x 之后,就能知道增加了多少个石头,然后每次操作是增加 k k k 个,做个除法就行。
  • 那么问题来了,我们代码里面怎么去设自变量 x x x?
    额,随便给一个大的常量就行了。 给小常量是不行的,因为这样比如可能导致上面例子中的 5 > x 5>x 5>x ,然后差分增加数字会比较麻烦。
  • 我怎么知道当前位置为 x x x 还是某个数字增加了一些值后的新值?
    可以看到,一定是每 k k k 个数字一起变成 x x x 的,我们记录一下 l a s t last last 表示第一位非 x x x 的位置即可。

【代码】

  • 时间复杂度: O ( T × n ) O(T\times n) O(T×n) 记得用差分处理区间增加
    91 / 1000 M s 91/1000Ms 91/1000Ms
/*
 _            __   __          _          _
| |           \ \ / /         | |        (_)
| |__  _   _   \ V /__ _ _ __ | |     ___ _
| '_ \| | | |   \ // _` | '_ \| |    / _ \ |
| |_) | |_| |   | | (_| | | | | |___|  __/ |
|_.__/ \__, |   \_/\__,_|_| |_\_____/\___|_|
        __/ |
       |___/
*/
const int MAX = 1e5+50;

ll aa[MAX];
ll de[MAX];
ll aim = (ll)2e9;
int main()
{
    int T;scanf("%d",&T);
    while(T--){
        ll mx = 0;
        int n,k;scanf("%d%d",&n,&k);
        for(int i = 0;i <= n + 1;++i)de[i] = 0;
        for(int i = 1;i <= n;++i){
            scanf("%lld",&aa[i]);
            mx = max(mx,aa[i]);
        }
        bool can = true;
        ll now = 0;
        int last = 0;
        for(int i = 1;i + k - 1 <= n;++i){		// 算前面的区间都要变成 aim
            now += de[i];
            ll shu = aa[i] + now;
            if(shu > aim){
                can = false;
                break;
            }else if(shu < aim){
                de[i+1] += aim - shu;
                de[i+k] -= aim - shu;
                if(i > last)
                    last = i + k - 1;
            }
        }
        ll ans = -1;
        if(last){
            for(int i = n - k + 2;i <= last;++i){		// 这些数字必须都是 aim
                now += de[i];
                ll shu = aa[i] + now;
                if(shu != aim){
                    can = false;
                    break;
                }
            }
            for(int i = last + 1;i <= n;++i){			// 这些都是常数,常数都要相同
                now += de[i];
                ll shu = aa[i] + now;
                if(ans == -1)ans = shu;
                else if(ans != shu){
                    can = false;
                    break;
                }
            }
        }
        if(!can)puts("-1");
        else {
            if(ans == -1)ans = mx;
            ll cha = 0;
            for(int i = 1;i <= n;++i){
                cha += ans - aa[i];
            }
            cha /= k;
            printf("%lld\n",cha);
        }
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值