思维|牛客练习赛51 E 数列 (二分 + 贪心 + 思维)

大致题意 思维|牛客练习赛51 E 数列 (二分 + 贪心 + 思维)
文章图片

思路 关键在于贪心的证明比较有启发性。
官方题解:
【思维|牛客练习赛51 E 数列 (二分 + 贪心 + 思维)】链接:https://ac.nowcoder.com/discuss/248083?type=101
对于一个数列,如果其一个子数列a[i],a[i+1]…a[j-1],a[j]满足a[k]=a[k-1]+1(i+1<=k<=j),同时,也满足i-1<1或者a[i]!=a[i-1]+1,j+1>n或者a[j+1]!=a[j]+1,则我们称这样的子数列为一段。我们可以发现,假如数列中有sum个这样的段,则小乔对数列的喜爱度为n-sum。因为数列是可以自由填数的,所以对与每一段我们使得a[i]=1是最优的,a[i],a[i+1]…a[j-1],a[j]显然。于是对于每一段,如果这段的长度为s,按最优的方法填数,则这一段的总和为s*(s+1)/2。
于是问题转换为:
把数列n分成sum个段,要求最小化sum,并且所有段的总和<=m。
设这sum段中长度最小的段长度为mn,长度最大的段长度为mx,可以发现如果我们已知了sum,那么要使这sum段的总和最小,就要使得mn和mx尽可能的接近,因为如果mx-mn>=2,那么段mx的最后一个元素的值为mx,段mn最后一个元素的值为mn,那么我们使段mx的长度-1,mn的长度加1。那么总和的变化就是总和=总和-mx+mn+1。因为mx-mn>=2,所以mx>mn+1。
因此如果得知sum,最优的策略就是均分所有段的长度。
均分的方法就是,首先使每一段的长度为n/sum,剩余n%sum个匀速均分配到n%sum个段上去。
于是有n%sum个段的长度为n/sum+1,n-n%sum个段的长度为n/sum。
对于sum,我们二分答案,因为答案具有单调性,分段越多,可以使总和越小。
通过二分答案求出sum再构造数列即可。时间复杂度O(logn+n)
代码

#include using namespace std; #define maxn 405 #define maxm 1000006 #define ll long long int #define INF 0x3f3f3f3f #define inc(i,l,r) for(int i=l; i<=r; i++) #define dec(i,r,l) for(int i=r; i>=l; i--) #define mem(a) memset(a,0,sizeof(a)) #define sqr(x) (x*x) #define inf (ll)2e18+1 int read(){ int x=0,f=1; char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=-1; ch=getchar(); } while(isdigit(ch))x=x*10+ch-'0',ch=getchar(); return f*x; } int n,m; bool pd(int x){ int len=n/x; int last=n%x; ll ss=1ll*len*(len+1)/2; ll ee=ss+len+1; return 1ll*(x-last)*ss+1ll*last*ee <= m; } int main() { n=read(); m=read(); int l=1,r=n,ans=1; while(l<=r){ int mid=(l+r)>>1; if(pd(mid)){ ans=mid; r=mid-1; } else l=mid+1; } //printf("ans = %d\n",ans); int p=n%ans; int len=n/ans; inc(i,1,p)inc(j,1,len+1)printf("%d ",j); inc(i,1,ans-p)inc(j,1,len)printf("%d ",j); printf("\n"); return 0; }

    推荐阅读