每个输入包含一个测试用例。
每个测试用例的第一行包含两个正整数,表示父母出差的天数N(N<=50000)和巧克力的数量M(N<=M<=100000)。
输出一个数表示小Q第一天最多能吃多少块巧克力。
3 7
4
import java.util.ArrayList; import java.util.Arrays; import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc=new Scanner(System.in); int n=sc.nextInt();//天数 int m=sc.nextInt();//巧克力个数 double min=0;//设置二分初始低位为0 double max=(double)m;//设置二分初始高位为初始巧克力个数m double stillHas=(double)m;//剩余巧克力个数 boolean flag=true; double temp; while(min<max)//当低位<高位时,开始二分查找 { temp=Math.ceil((min+max)/2);//取低位和高位中间的一位向上取整,即为试探的第一天吃的巧克力数 for(int i=1;i<n+1;i++)//循环n天 { if(temp<=stillHas)//当前天需要吃的巧克力个数<=剩余巧克力个数时,减少巧克力,同时第二天巧克力个数变为第一天的一半 { stillHas=stillHas-temp; temp=Math.ceil(temp/2); } else//当前天需要吃的巧克力个数>剩余巧克力个数时,说明没有撑到n天巧克力吃完,置flag=false;跳出循环 { flag=false; break; } } if(flag)//flag==true,说明上面的for循环正常循环结束跳出,说明当前的第一天吃的Math.ceil((min+max)/2)个巧克力满足要求 { //判断一下,如果比Math.ceil((min+max)/2)个巧克力大1个巧克力时 //isTrue返回false说明再大1个巧克力就不满足要求了,那么当前的Math.ceil((min+max)/2)就是最大的第一天吃的巧克力数,输出即可 if(!isTrue(n,m,Math.ceil((min+max)/2)+1)) { System.out.println((int)Math.ceil((min+max)/2)); return; } //如果大1个巧克力仍然满足要求那么说明当前的第一天吃的Math.ceil((min+max)/2)取小了应取大一点的巧克力数,需要继续二分查找 else { min=Math.ceil((min+max)/2);//取低位为当前的试探的第一天吃的巧克力数 stillHas=(double)m;//重置剩余巧克力数为总数 } } //flag==false,说明上面的for循环遇到break跳出,说明当前的第一天吃的Math.ceil((min+max)/2)取大了应取小一点的巧克力数,需要继续二分查找 else { max=Math.ceil((min+max)/2);//取高位为当前的试探的第一天吃的巧克力数 stillHas=(double)m;//重置剩余巧克力数为总数 flag=true;//重置标志位 } } } //用于判断当每天吃X个巧克力时是否能撑到父母回来的静态方法 public static boolean isTrue(int n,double m,double x) { for(int i=1;i<n+1;i++) { if(x<=m) { m=m-x; x=Math.ceil(x/2); } else { return false; } } return true; } }
二分查找求第一天可能吃的最多糖数
n,m = map(int, input().split()) def countSuger(num, k): res = 0 while k > 0: res += num num = num // 2 + num % 2 k -= 1 return res left , right = 0, 100000 while left < right: mid = (left + right) // 2 + 1 if countSuger(mid, n) <= m: left = mid else: right = mid - 1 print(left)
二分查找法:修改了一下大佬的边界条件
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
int n=sc.nextInt();
int[] array= new int [n];
int M=sc.nextInt();
M=M-n;
for(int i=0;i<n;i++)
array[i]=1;
while(--M>=0)
{ array[0]++;
for(int i=0;i<n-1;i++)
{
if(2*array[i+1]<array[i])
{
array[i]--;
array[i+1]++;
}
if(array[i+1]==1&& array[i]==1)
break;
}
}
System.out.println(array[0]);
// System.out.println(Arrays.toString(array));
}
}
在域外創音大佬的基础上做了一点修改:
#include<iostream>
#define MAX_INDEX 17
int power2[] = {1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072};
int main()
{
int n,m;
std::cin>>n>>m;
//只有1天时直接输出
if(n==1){std::cout<<m<<std::endl;return 0;} //初始分配:每天最少吃1块
int alignable = m-n;
int result = 1; //如果可以某种power2[i]-1的方式分配,便进入。具体的分配是:第一天多吃power2[i-1]块,第二天多吃power2[i-2]块,依此类推。
//这样一来,只要每个追加分配所对应的i不重复,每天吃的块数就依然能符合要求,因为第j天吃的块数最多等于power2[从i-j到0]+1,一定大于power2[从i-j-1到0]的二倍。
for(int i=MAX_INDEX-1;i>=0;i--)if(alignable>=power2[i]-1)
{
if(i>0)//防止越界
result+=power2[i-1];
int assigned = 0;
if(i>n)
assigned = (power2[i]-1) - (power2[i-n]-1);
else
assigned = (power2[i]-1);
alignable -= assigned;
}
std::cout<<result<<std::endl;
return 0;
}
原代码找到一个测试用例存在问题:(3天,20块),可以得到按照(11,6,3)分配为最优解,而原代码得出结果10;可以找到问题出在原代码18行,当天数n小于i时,原代码的已分配数量过多。
另外17行的i-1,在下愚钝认为有可能越界,加上了判别条件(但是使用牛客的OJ并没有发现会真的越界导致出错)。
#include
#define MAX_INDEX 17
int power2[] = {1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072};
int main()
{
int n,m;
std::cin>>n>>m;
//只有1天时直接输出
if(n==1){std::cout<<m<<std::endl;return 0;}
//初始分配:每天最少吃1块
int alignable = m-n;
int result = 1;
//如果可以某种power2[i]-1的方式分配,便进入。具体的分配是:第一天多吃power2[i-1]块,第二天多吃power2[i-2]块,依此类推。
//这样一来,只要每个追加分配所对应的i不重复,每天吃的块数就依然能符合要求,因为第j天吃的块数最多等于power2[从i-j到0]+1,一定大于power2[从i-j-1到0]的二倍。
for(int i=MAX_INDEX-1;i>=0;i--)if(alignable>=power2[i]-1)
{
result+=power2[i-1];
alignable -= (power2[i]-1);
}
std::cout<<result<<std::endl;
return 0;
}
可以从分配的角度来看,这样的算法可以把时间复杂度和空间复杂都减少到O(log(m))
1)得先保证后面每天都有巧克力吃,每天基础值为1。如果有多的,再逐步提高第一天的门槛。用一个双端有界队列实现。2)为了让更多巧克力集中在第一天,应该用最少的天数快速将巧克力数量提升到最大(最大是我的目标,因为每天不能少于前一天的一半,我得慢慢铺垫这个最大值,但是铺垫不能占用我太多巧克力),因此等比q就是2。3)有多的就往第一天加。因为每天不能少于前一天的一半,给第一天加1可能引起后面多天级联加1,这种级联就和加法进位一样,只不过是加两次才进。可以用boolean标记,而且,每加1就需要一块巧克力。如果给第一天加巧克力不足以支持整个级联关系需要的巧克力,那这一趟就不能加了,刚刚第一天的数就已经是最大的了。import java.util.LinkedList; import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); int d = in.nextInt(); int m = in.nextInt(); LinkedList<Integer> q = new LinkedList<>(); //做有界队列 for(int i = d;i>0;i--){ //先将队列填满1,保证每天都有得吃 q.add(1); m--; } //假如可以“滚动队列”,即去掉队尾最小的元素,然后在对头添加一个更大的,那就这么干。 while(true){ int t = q.peek(); if( m+q.peekLast() >= t*2 ){ m += q.pollLast(); q.addFirst(t*2 ); m -= t*2 ; }else break; } //巧克力恰好耗尽,不需要继续了 if(m==0){ System.out.println(q.peek()); return; } // 把剩余不够凑成一个更大的值的数巧克力分散到已经有的数中 boolean[] full = new boolean[d]; //LinkedList 数据转数组,方便获取下一个元素 int[] base = new int[d]; for(int i = 0;i<d;i++){ base[i] = q.poll(); if(base[i]>1) full[i] = true; else full[i] = false; } while(m>0){ int i = 0; while (i<d){ base[i]++; m--; if(full[i]){ full[i] = false; i++; continue; }else{ full[i] = true; break; } } } if(m==0) System.out.println(base[0]); else System.out.println(base[0]-1); } }
#include <bits/stdc++.h> using namespace std; int n,m; bool check(int max_eat){ int sum = max_eat; int t_eat = max_eat; for(int i = 1;i < n ; i++){ if(t_eat%2!=0){ t_eat = (t_eat+1) / 2; }else t_eat = t_eat/ 2; sum += t_eat; } return sum > m; } int main(void){ cin>>n>>m; if(n==1){ cout<<m<<endl; return 0; } int l = 1 , r = m; while(l < r){ int mid = (l + r) >> 1; if(check(mid)){ r = mid; }else{ l = mid + 1; } } cout<< l - 1 ; return 0; }
var p=readline().split(' ').map(value=>+value); let m=p[0],n=p[1]; let max=0; let flag=0; let start=0,end=n; while(start<=end){ let mid=Math.ceil((start+end)/2) let sum=mid; let num=mid; for(var j=1;j<m;j++){ num=Math.ceil(num/2) sum+=num; } if(sum>n){ end=mid-1; }else{ start=mid+1; } sum=0; } print(start-1)二分查找
#include<iostream> #include<algorithm> #include<functional> using namespace std; int cal(int cur, int n); int binary_s(int n, int m); int main(){ int n, m; cin>>n>>m; cout<<binary_s(n, m)<<endl; return 0; } int binary_s(int n, int m){ //二分查找 int l = 1, r = m, mid; while(l < r){ mid = (l + r + 1) >> 1; if(cal(mid, n) == m) return mid; if(cal(mid, n) < m) l = mid; else r = mid - 1; } return l; } int cal(int cur, int n){ int sum = 0; for(int i = 1; i <= n; i++){ sum += cur; cur = (cur + 1)>>1; } return sum; }
n,m = map(int, input().split(" ")) def SumEat(e1,N): S = 0 e = e1 for i in range(0,N): S += e e = (e+1)//2 return S def BinarySearch(N,M): if N == 1: return M low = 1 high = M while low<high: mid = (low+high+1)//2 if SumEat(mid,N)<=M: low = mid else: high = mid -1 return low print(BinarySearch(n,m))
import syss = list(sys.stdin.readline().split(' '))
return count
#include <iostream> using namespace std; void back_order(int& count, int& idx, int max_idx, int depth, int max_depth) { if (idx>max_idx) { return; } if (depth == max_depth) { count++; return; } else { idx++; back_order(count, idx, max_idx, depth + 1, max_depth); idx++; back_order(count, idx, max_idx, depth + 1, max_depth); } } int main() { int days, foods; while (cin >> days >> foods) { int res = 0; if (days <= 31) { //考虑树直接满了的情况 int full_foods = (1 << days) - 1; int full_tree = foods / full_foods; res += full_tree * (1 << (days - 1)); foods = foods % full_foods; } int idx = 1; back_order(res, idx, foods, 1, days); cout << res << endl; } return 0; }
二叉树遍历解决问题
#include<iostream> using namespace std; //a^p int pow(int a,int p) { if(p==0) return 1; int re=1; while(p!=0) { if(p&1==1) re*=a; a*=a; p>>=1; } return re; } /*主要解法为求出result的可能的最小值及最大值,再从中遍历,其中利用等比数列的公式*/ /*在巧克力不足的情况下(最后几天会只吃一块) *最大值对应数列为1,2,4,8,16... *最小值对应数列为2,3,5,9,17... */ int main() { int n,m; cin>>n>>m; int base=m-n+1; int i,j,k,max,min; if(n<30&&(int)(m/(pow(2,n)-1.0)+0.5)>1) //巧克力充足 { max=(int)(m/(pow(2,n)-1.0)+0.5); min=(int)(m/(pow(2,n)-1.0)); } else //巧克力不足 { for(i=0;pow(2,i)-i<=base;i++); max=pow(2,i-1); for(i=0;pow(2,i)<=base;i++); min=pow(2,i-2)+1; } int sum=0; for(i=min;i<=max;i++) { sum=0; k=i; for(j=0;j<n;j++) { sum+=k; k=(k+1)>>1; } if(sum>m) break; } cout<<i-1<<endl; return 0; }
import math def calc_total_chocolate(first_day_chocolate, days): total_chocolate = 0 chocolate_of_day = first_day_chocolate for i in range(days): total_chocolate += chocolate_of_day chocolate_of_day = (chocolate_of_day + 1) >> 1 # 当某一天吃的巧克力等于1时,之后每一天吃的巧克力都为1,可以直接计算出之后每一天吃的总的巧克力 if chocolate_of_day == 1: total_chocolate += days - i - 1 break return total_chocolate def find_first_day_max_chocolate(m, n): # 通过等比数列计算求和公式得到第一天吃的巧克力的最大下限 # 也就是说,真的每一天吃的巧克力数要大于这个下限 first_day_chocolate = 1 + math.ceil((m - n + 1) / 2) while 1: total_chocolate = calc_total_chocolate(first_day_chocolate, n) if total_chocolate > m: first_day_chocolate -= 1 break elif total_chocolate < m: first_day_chocolate +=1 else: break return first_day_chocolate if __name__ == '__main__': n, m = map(int, input().strip().split()) print(find_first_day_max_chocolate(m, n))
n,m = map(int, input().split()) if n == 1: print(m) exit() for i in range(m-n+1,1,-1): t = i s = 0 r = 0 while t!=1: s += t r += 1 t = (t+1)//2 if m-s >= n-r: print(i) break
148 ms | 3440K | Python 3 |
#include<iostream> using namespace std; int n,m; //计算第一天吃s个巧克力一共需要多少个多少个巧克力 int sum(int s){ int sum=0; for(int i=0;i<n;i++){ sum+=s; s=(s+1)>>1;//向上取整 } return sum; } //二分查找 int fun(){ if(n==1) return m; int low=1; int high=m;//第一天的巧克力一定是大于等于1小于等于m的 while(low<high){ int mid=(low+high+1)>>1;//向上取整 if(sum(mid)==m) return mid;//如果第一天吃mid个巧克力,刚刚好吃完所有巧克力,那么直接返回 else if(sum(mid)<m){ low=mid; }else{ high=mid-1; } } return high; } int main() { cin>>n>>m; int res=fun(); cout<<res<<endl; return 0; }
//二分查找,修改大佬们的边界,直接返回大于目标值的第一个下标
import java.util.*;
public class Main {
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
System.out.println(process(n, m));
}
public static int process(int n, int m){
int l = 1;
int r = m;
while(l <= r){
int mid = l + (r - l) / 2;
if(sum(mid, n) > m){
r = mid - 1;
}else{
l = mid + 1;
}
}
return l - 1;
}
public static int sum(int s, int n){
int sum = 0;
for(int i = 0; i < n; i++){
sum += s;
s = (s + 1) / 2;
}
return sum;
}
}