BZOJ3203 [Sdoi2013] 保护出题人
@(BZOJ)[凸包, 三分法]
Description
Input
第一行两个空格隔开的正整数n和d,分别表示关数和相邻僵尸间的距离。接下来n行每行两个空格隔开的正整数,第i + 1行为Ai和 Xi,分别表示相比上一关在僵尸队列排头增加血量为Ai 点的僵尸,排头僵尸从距离房子Xi米处开始接近。
Output
一个数,n关植物攻击力的最小总和 ,保留到整数。
Sample Input
5 2
3 3
1 1
10 8
4 8
2 3
Sample Output
7
HINT
第一关:距离房子3米处有一只血量3点的僵尸,植物最小攻击力为1.00000; 第二关:距离房子1米处有一只血量1点的僵尸、3米处有血量3点的僵尸,植物最小攻击力为1.33333; 第三关:距离房子8米处有一只血量10点的僵尸、10米处有血量1点的僵尸、12米处有血量3点的僵尸,植物最小攻击力为1.25000; 第四关:距离房子8米处有一只血量4点的僵尸、10米处有血量10点的僵尸、12米处有血量1点的僵尸、14米处有血量3点的僵尸,植物最小攻击力为1.40000; 第五关:距离房子3米处有一只血量2点的僵尸、5米处有血量4点的僵尸、7米处有 血量10点的僵尸、9米处有血量1点的僵尸、11米处有血量3点的僵尸,植物最小攻击力 为2.28571。 植物攻击力的最小总和为7.26905。
对于100%的数据, 1≤n≤105,1≤d≤1012,1≤x≤ 1012,1≤a≤1012
Solution
令\(sum_i = sum_{i - 1} + a_i\), 則有對於第\(i\)次攻擊, 有$$f_i \ge max \left( \frac{sum_i - sum_{j - 1}}{x_i + d * (i - j)} \right)(1 \le j \le i)$$
維護一個斜率單調遞增的凸包, 每個點的坐標為\((i*d,sum_{i-1})\). 對於每一個\(i\)用三分法找到凸包中與當前點\((x_i + i * d, sum_i)\)的連線斜率最大的點即可.
注意在整數上三分的方法
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define eps 1e-6
#define LL long long
#define mid1 l+(r-l)/3
#define mid2 l+(r-l)/3*2
const int N=100010;
int n,siz;
double ans,d,sum[N],xi[N];
struct Point
{
double x,y;
} q[N];
Point operator - (Point x,Point y)
{
return (Point)
{
x.x-y.x, x.y-y.y
};
}
inline double Cross(Point x,Point y)
{
return x.x*y.y-x.y*y.x;
}
inline double calc(Point x)
{
int l=1,r=siz,i;
double maxn=0,k1,k2;
while(l+2<r)
{
k1=(x.y-q[mid1].y)/(x.x-q[mid1].x);
k2=(x.y-q[mid2].y)/(x.x-q[mid2].x);
maxn=max(maxn,max(k1,k2));
if(k1<k2)
l=mid1;
else
r=mid2;
}
for(i=l; i<=r; ++i)
maxn=max(maxn,(x.y-q[i].y)/(x.x-q[i].x));
return maxn;
}
int main()
{
int i,j;
scanf("%d%lfd",&n,&d);
for(i=1; i<=n; ++i)
{
scanf("%lf%lf",&sum[i],&xi[i]);
sum[i]+=sum[i-1];
}
ans=sum[1]/xi[1];
q[siz=1]=(Point)
{
d,sum[0]
};
for(i=2; i<=n; ++i)
{
Point x=(Point)
{
i*d,sum[i-1]
};
while(siz>=2&&Cross(q[siz]-q[siz-1], x-q[siz-1])<=0)
--siz;
q[++siz]=x;
x=(Point)
{
xi[i]+i*d, sum[i]
};
ans+=calc(x);
}
printf("%.0f\n",ans);
}