codeforces1157D 暴力 + 二分

题目传送门

题意:

让你构造一个包括 k 个正整数的序列 \dpi{150}a 。

这个序列满足:

(1)\sum_{i=1}^{k} a_i = n

(2) a_i < a_{i + 1} \leqslant 2 * a_i \;,\; 1 \leqslant i \leqslant k - 1 。

数据范围: 1 \leqslant n \leqslant 10^9 \;,\; 1 \leqslant k \leqslant 10^5 。

题解:

设首项为 x ,假设 n \in [kx + \frac{k(k-1)}{2} , x(2^k-1)] 成立,那么就有解。

区间的左端点由首项为 x ,公差为 1 的等差数列求和得来。

区间的左端点由首项为 x ,公比为 2 的等比数列求和得来。

我们可以对首项进行二分,看看能不能找到一个首项使这个序列存在。

二分的方式就是找到最大的 x 使 kx + \frac{k(k-1)}{2} \leqslant n 成立,然后我们就确定了首项 x 。

我们可以用这样的方式确定每一个数。

例如第 1 项确定后,我们可以把第 2 项看成首项,进行这样的二分操作。重复进行就好了。

最后再 check 构造的序列是否符合题意。

感受:

这种构造题现在来说是弱项。

代码:

#include<bits/stdc++.h>
using namespace std ;
typedef long long ll ;
const int maxn = 1e5 + 5 ;
ll n , k , a[maxn] ;
ll tn , tk ;
ll cal(ll x)
{
	return k * x + k * (k - 1) / 2 ;
}
bool ok(ll x)
{
	return cal(x) <= n ;
}
bool judge()
{
	ll sum = a[1] ;
	if(a[1] <= 0)  return 0 ;
	if(sum > tn)  return 0 ;
	for(int i = 2 ; i <= tk ; i ++)
	{
		sum += a[i] ;
		if(sum > tn)  return 0 ;
		if(a[i] < a[i - 1] + 1 || a[i] > a[i - 1] * 2)
		  return 0 ;
	}
	return sum == tn ;
}
void solve(int i)
{
	ll l = a[i - 1] + 1 , r = a[i - 1] * 2 ;
	ll ans = l ;
	if(i == 1)  l = 1 , r = 1e9 ;
	while(l <= r)
	{
		ll mid = (l + r) / 2 ;
		if(ok(mid))  ans = mid , l = mid + 1 ;
		else  r = mid - 1 ;
	}
	a[i] = ans ;
	n -= ans , k -- ;
}
int main()
{
	int num = 0 ;
	scanf("%lld%lld" , &n , &k) ;
	tn = n , tk = k ;
	while(k) 
	  solve(++ num) ;
	if(!judge()){printf("NO\n") ;  return 0 ;}
	printf("YES\n") ;
	for(int i = 1 ; i <= tk ; i ++)  printf("%lld " , a[i]) ;
	printf("\n") ;
	return 0 ;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值