Npc51 E 数列 [构造题, 二分答案]

36 篇文章 0 订阅
6 篇文章 0 订阅

数 列 数列

题目描述见链接 .


正 解 部 分 \color{red}{正解部分}

首先考虑怎么划分序列,
可以想到: 最优划分方法 会将 序列 分为连续的几段 上升序列 .

若存在下降序列, 可以将其倒置, 答案会更优 .

设划分了 c n t cnt cnt上升序列, 则 显然 a n s = N − c n t ans = N-cnt ans=Ncnt,

现在要使得 a n s ans ans 尽可能大, 就要使得 c n t cnt cnt 尽量小,

考虑 c n t cnt cnt 受什么限制, 受 M M M 的大小限制,
具 体 来 说 具体来说 : 设每个 上升序列 的长度为 x i x_i xi, 则受到的限制为 s u m = ∑ ( 1 + x i ) x i 2 ≤ M sum = \sum \frac{(1+x_i)x_i}{2} \le M sum=2(1+xi)xiM .

此时可以想到, 若 c n t cnt cnt 可以合法地构造, 那么 c n t + 1 cnt+1 cnt+1 也一定可以合法地构造, 换句话说, c n t cnt cnt 具有单调性 .

c n t cnt cnt上升序列 中的一段上升序列分割为两个, s u m sum sum 只会更小, 所以不会不合法 .

于是 二分答案, 二分 c n t cnt cnt, 考虑如何 c h e c k ( ) check() check(),

s u m sum sum 最小值大于 M M M 时, 说明 c n t cnt cnt 一定不合法, 依此进行判断,

考虑 c n t cnt cnt 固定的时候, s u m sum sum 什么时候最小, 显然当每个 x i x_i xi 都尽可能均匀时, s u m sum sum 最小 .

设最大的 x i x_i xi x m a x x_{max} xmax, 最小的 x i x_i xi x m i n x_{min} xmin, 则当把 x m a x x_{max} xmax长度 减 1 1 1, 把 x m i n x_{min} xmin 长度 加 1 1 1,
s u m sum sum 的变化值为 − x m a x + ( x m i n + 1 ) -x_{max} + (x_{min}+1) xmax+(xmin+1), 其中 x m i n + 1 ≤ x m a x x_{min}+1 \le x_{max} xmin+1xmax, 所以这么操作 s u m sum sum 不会更大 .

于是对每个 x i x_i xi 赋初值 N c n t \frac{N}{cnt} cntN, 剩下 N % i N \% i N%i 均匀散布到前面 c n t cnt cnt 个长度为 N c n t \frac{N}{cnt} cntN x i x_i xi 中,
得到 x i x_i xi, 计算 s u m sum sum, 判断是否小于等于 M M M 即可 .

时间复杂度 O ( log ⁡ n + n ) O(\log n + n) O(logn+n)


实 现 部 分 \color{red}{实现部分}

#include<bits/stdc++.h>
#define reg register
typedef long long ll;

const int maxn = 1e5 + 5;

int N;
int M;
int min_cnt;

bool chk(int cnt){
        int x_1 = N/cnt, x_2 = N/cnt + 1;
        int num_1 = cnt - N%cnt, num_2 = N%cnt;
        ll s = (1ll+x_1)*x_1/2*num_1 + (1ll+x_2)*x_2/2*num_2;
        return s <= 1ll*M;
}

int main(){
        scanf("%d%d", &N, &M);
        int l = 1, r = N; min_cnt = N;
        while(l <= r){
                int mid = l+r >> 1;
                if(chk(mid)) min_cnt = std::min(mid, min_cnt), r = mid - 1;
                else l = mid + 1;
        }
        int x_1 = N/min_cnt, x_2 = N/min_cnt + 1;
        int num_1 = min_cnt - N%min_cnt, num_2 = N%min_cnt;
        for(reg int i = 1; i <= num_1; i ++)
                for(reg int j = 1; j <= x_1; j ++) printf("%d ", j);
        for(reg int i = 1; i <= num_2; i ++)
                for(reg int j = 1; j <= x_2; j ++) printf("%d ", j);
        return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值