【BZOJ3203】[Sdoi2013]保护出题人 二分+凸包

时间:2022-01-15 09:25:20

【BZOJ3203】[Sdoi2013]保护出题人

Description

【BZOJ3203】[Sdoi2013]保护出题人 二分+凸包

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≤10^5,1≤d≤10^12,1≤x≤ 10^12,1≤a≤10^12

题解:只考虑一次询问,我们将每个僵尸的血量看成它前面所有僵尸的血量之和,那么所有僵尸就是同时受到伤害的了。此时可以将每个僵尸看成一个点(距离,血量和),我们要求的就是满足所有x*k-y>=0的最小的k。

可以先用单调栈来维护所有点构成的上凸包,然后查询时找到原点到这个凸包的切线即可。找切线可以用二分(然而网上题解都是三分,你要想到把一个单峰函数求了导以后就变成单调函数了啊)。

但是每次的僵尸是从前面加入的,那么我们将原点平移,原来的点不动即可。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn=100010;
typedef long long ll;
typedef double ld;
int n,t;
ld d,sum,ans;
ld a,b,x[maxn],y[maxn];
int s[maxn];
int main()
{
scanf("%d%lf",&n,&d);
int i,l,r,mid;
for(i=1;i<=n;i++)
{
scanf("%lf%lf",&a,&b),x[i]=-sum,sum+=a,y[i]=-i*d;
while(t>1&&(y[i]-y[s[t]])/(x[i]-x[s[t]])>(y[s[t]]-y[s[t-1]])/(x[s[t]]-x[s[t-1]])-1e-15) t--;
s[++t]=i;
l=1,r=t,x[0]=-sum,y[0]=-i*d-b;
while(l<r)
{
mid=(l+r)>>1;
if((y[s[mid]]-y[s[mid+1]])/(x[s[mid]]-x[s[mid+1]])>(y[s[mid+1]]-y[0])/(x[s[mid+1]]-x[0])-1e-15) l=mid+1;
else r=mid;
}
ans+=(x[s[l]]-x[0])/(y[s[l]]-y[0]);
}
printf("%.0lf",ans);
return 0;
}//5 2 3 3 1 1 10 8 4 8 2 3