题意: n个人围成一圈,第i个人想要ri个不同的礼物,相邻的两个人礼物类型不能重复。
每种礼物不限量,求最少需要多少种礼物才行(1<=n<=100000)
训练指南, 37页。 分类讨论,n为偶数和奇数时。
n为偶数情况, p = max{r[i] + r[i + 1]} (1 <= i <= n) //记 r[n + 1] = r[1]
本题要点:
1、贪心来分析奇数情况
对应第一人,需要礼物 r[1], 用 r[1] 将 所有礼物分为两部分,
1 ~ r[1] 和 r[1] + 1 ~ p
int Left[MxaN]; //left[i] 表示第i个人 在 [1 ~ r1] 范围内取了多少个礼物
int Right[MxaN]; //right[i] 表示第i个人 在 [r1 + 1 ~ p] 范围内取了多少个礼物
假设第一个人 r[1] 个礼物全部取左边,也就是 left[1] = r[1], right[1] = 0;
后面的 人, i为奇数时候,尽量往右区间[r1 + 1 ~ p]取礼物,i为偶数时候,尽量往左[1 ~ r1]区间取礼物
如果最后能满足,left[n] == 0, 也就是 最后一人,取的礼物与 第一个人没有重复,说明 p 个礼物满足条件
2、二分查找 礼物总数 p, 找到最小的礼物总数
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int MxaN = 100010;
int n;
int r[MxaN];
int Left[MxaN]; //left[i] 表示第i个人 在 [1 ~ r1] 范围内取了多少个礼物
int Right[MxaN]; //right[i] 表示第i个人 在 [r1 + 1 ~ p] 范围内取了多少个礼物
//这里的p表示当前一共有多少个礼物
bool judge(int p)
{
int x = r[1], y = p - r[1];
Left[1] = x, Right[1] = 0; //第一人,往左边取礼物
for(int i = 2; i <= n; ++i)
{
if(i % 2 == 1) //i为奇数,往右边取
{
Right[i] = min(y - Right[i - 1], r[i]); //每个人最多取 r[i]个礼物
Left[i] = r[i] - Right[i]; //另一边取的礼物数量
}else{ //i为偶数,往左边取
Left[i] = min(x - Left[i - 1], r[i]);
Right[i] = r[i] - Left[i];
}
}
return Left[n] == 0;
}
void solve()
{
if(1 == n)
{
printf("%d\n", r[1]);
return;
}
r[n + 1] = r[1];
int L = 0, R = 0; //L为 下限值,R 为上限值
for(int i = 1; i <= n; ++i)
{
L = max(L, r[i] + r[i + 1]);
}
if(n % 2 == 1) //为奇数时
{
for(int i = 1; i <= n; ++i)
{
R = max(R, 3 * r[i]);
}
while(L < R)
{
int M = (R + L) / 2;
if(judge(M))
{
R = M;
}else{
L = M + 1;
}
}
}
printf("%d\n", L);
}
int main()
{
while(scanf("%d", &n) == 1 && n)
{
for(int i = 1; i <= n; ++i)
{
scanf("%d", &r[i]);
}
solve();
}
return 0;
}
/*
3
4
2
2
5
2
2
2
2
2
5
1
1
1
1
1
0
*/
/*
8
5
3
*/