算法竞赛入门经典训练指南打卡
题目链接:UVALive 3177
思路
首先,毋庸置疑,肯定相邻的两个人是要分开拿礼物的,即不能拿到同一种,那么
1.对于偶数个人来说,只要第一个人和最后一个人完全可以按照奇偶错开
比如第一个人拿1 2 ,第二个人拿 3 4 ,第三个人又可以拿 1 , 2
所以我们只需要找到相邻的两个人所要礼物加起来最多的礼物数即可
定义i遍历数组 ans = max(r[i] + r[i + 1] , ans)
2.对于奇数个人来说,如果只是奇偶错开,则第一个人和最后一个人会重复
所以我们需要使用二分查找来找出合适的结果
那么对于奇数来说,可以让相邻的两个人错开拿,通过枚举来查看
第一个人拿前面几个礼物
奇数位的人尽量拿后面几个礼物(尽量指不与前面的人重复的情况下)
偶数位的人尽量拿前面几个礼物
则最后一个人,会尽量拿到后面几个礼物和第一个人错开
具体细节看代码注释
代码如下:
#include <iostream>
#define ton(i , n) for(int i = 1 ; i <= n ; ++ i)
using namespace std ;
const int max_n = 100010 ;
int n , r[max_n] , Left[max_n] , Right[max_n] ;
bool check(int mid){
int x = r[1] , y = mid - r[1] ; //第一个人拿前r[1]个 , 剩余的礼物数为mid - r[1]个
Left[1] = x ; //前x个礼物为左边,则第一个人从左边拿了x个礼物
Right[1] = 0 ; //第一个人没有拿右边的礼物
for(int i = 2 ; i <= n ; ++ i){
if(i % 2 == 1){
Right[i] = min(y - Right[i - 1] , r[i]) ; //第奇数个人尽量拿右边的礼物
Left[i] = r[i] - Right[i] ;
}
else{
Left[i] = min(x - Left[i - 1] , r[i]) ; //第偶数个人尽量拿左边的礼物
Right[i] = r[i] - Left[i] ;
}
}
return Left[n] == 0 ; //最后一个人没有拿到钱左边的礼物,即没有和第一个人重复,说明可以
}
int main(){
while (cin >> n && n){
ton(i , n)
cin >> r[i] ;
if(n == 1){
cout << r[1] << endl ;
continue ; //如果n == 1,则这个人要几个礼物就得准备几个礼物
}
r[n + 1] = r[1] ; //行成环,最后一个人和第一个人相邻
int L = 0 , R = 0 ;
ton(i , n)
L = max(L , r[i] + r[i + 1]) ; //找到需要准备的礼物的最小值
if(n % 2 == 1){ //如果是奇数,则二分查找,如果是偶数,则上面的L就是结果
ton(i , n)
R = max(R , 3 * r[i]) ; //找到需要准备的最大值(实际不可能达到)
while (L < R){ //二分查找,找到合适的值,存放在L中
int mid = L + (R - L) / 2 ;
if(check(mid))
R = mid ;
else
L = mid + 1 ;
}
}
cout << L << endl ; //输出结果
}
return 0 ;
}