链接: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;
}