牛客练习赛51 E 数列

链接:https://ac.nowcoder.com/acm/contest/1083/E
来源:牛客网

题目描述

小乔有一个长度为n的整数数列,最开始里面所有的值都为0,小乔需要将在1…n的每一个位置填入一个大于0的正整数,得到一个新的数列,并且这个数列所有数的和不超过m,小乔对这个数列会有一个喜爱度,小乔对这个数列的喜爱度为满足2<=i<=n并且a[i]=a[i-1]+1的i的个数。现在给出n,m,请你制定一种填数方案,最大化小乔对数列的喜爱度。方案可能有多种,你只需要输出任意一种即可。

输入描述:

第一行两个整数n,m。1<=n<=1e5,n<=m<=1e9。

输出描述:

一行n个整数,表示位置1…n填的数。

思路:

这道题首先可以二分答案,也就是满足条件的下标个数,我们可以换种方式来理解他:数列中出现的断层数量,下图表示的就是样例中的情况,假使数列为直接的1-n序列,那答案显然是n-1,而如果其中每出现一个断层,则答案减一,而每个断层开始自然是1最优,因此我们需要确定的就是最少的断层数
在这里插入图片描述
那么为什么具有二分性呢,很显然,一个有x个断层的序列,那对于x+1个断层,我只要在原序列中随意一个断层中再断一次就能严格减小花费,因此二分是合理的

然后我们的check函数中要做的就是确定构成该数目断层需要的最少数字之和,那么这里又是很容易就能想到,最长的断层和最短的断层长度差值不会超过1,为什么?
如下图,有一个123412的数组,那我把4去掉之后在第二个2后面加一个3,断层数不变,但是总花费减小了,所以我们发现高的断层补低的断层可以减小花费,那么不断补之后的结果就是高度差小于等于1
在这里插入图片描述
所以对于当前的断层数,我们可以通过向下取整来求得较低的断层的高度,再取余解出较高的断层个数,加减乘除一下返回最小花费:

bool check(int x){
    int cnt=n/(x+1);//较低的高度
    int cct=n%(cnt*(x+1));//较高断层的个数
    int num=(x+1)*s[cnt]+cct*(cnt+1);//每个较高的断层,相当于在较低的断层后加了一列cnt+1,因此乘上个数再加上
    return num<=m;//判断合理性

}

找到了最小断层数以后,就可以通过之前求解的高度和两种断层的个数直接输出了:

void solve(int x){
    int cnt=n/(x+1);//高度
    int cct=n%(x+1);
    int pos=0;
    for(int i=1;i<=cct;i++){//cct个较高断层
        for(int j=1;j<=cnt+1;j++){
            as[++pos]=j;
        }
    }
    for(int i=1;i<=x+1-cct;i++){//x+1-cct个较低断层
        for(int j=1;j<=cnt;j++){
            as[++pos]=j;
        }
    }
    for(int i=1;i<=pos;i++){
        printf("%d",as[i]);if(i!=pos)printf(" ");
    }
}

完整代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<bits/stdc++.h>
using namespace std;
#define PB push_back
#define LL long long
#define FI first
#define SE second
#define POP pop_back()
#define PII pair<int,int>
#define endl '\n'
#define ls x<<1
#define rs x<<1|1
#define m(x) a[x].l+a[x].r>>1
#define ST cin>>n;for(int i=1;i<=n;i++)scanf("%d",&a[i]);
#define debug cout<<"FUCK"<<endl;
#define loop(i,n) for(int i=1;i<=n;i++)
const int N=2e5;
int n;
LL m;
LL s[N];
bool check(int x){
    LL cnt=n/(x+1);
    LL cct=n%(cnt*(x+1));
    LL num=(x+1)*s[cnt]+cct*(cnt+1);
    return num<=m;

}
int as[N];
void solve(int x){
    int cnt=n/(x+1);
    int cct=n%(x+1);
    int pos=0;
    for(int i=1;i<=cct;i++){
        for(int j=1;j<=cnt+1;j++){
            as[++pos]=j;
        }
    }
    for(int i=1;i<=x+1-cct;i++){
        for(int j=1;j<=cnt;j++){
            as[++pos]=j;
        }
    }
    for(int i=1;i<=pos;i++){
        printf("%d",as[i]);if(i!=pos)printf(" ");
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        s[i]=s[i-1]+i;
    }
    if(m>=s[n]){
        for(int i=1;i<=n;i++){
            printf("%d",i);
            if(i!=n)printf(" ");
        }
        return 0;
    }
    if(m<=n/2+n){
        int cnt=n;
        for(int i=1;i<=n;i++){
            if(i%2==0&&cnt<m){
                cnt++;
                printf("%d",2);
            }
            else printf("1");
            if(i!=n)printf(" ");
        }
        return 0;
    }
    int ans=n;
    int l=0,r=n/2;
    while(l<=r){
        int mid=l+r>>1;
        if(check(mid)){
            ans=min(ans,mid);
            r=mid-1;
        }
        else l=mid+1;
    }
    solve(ans);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值