第一次写三分,比想象的要艰辛啊…
因为这道题是在搜索「三分」的时候发现的,于是直接就看到题解了…
不过看题解的时候快要困的睡着了…看半天半懂不懂的….
粗略推导了一下之后就开始写了,于是各种小bug= =
精神状态差的时候果然要拒绝写题!!!
BZOJ3203传送门
输入格式:
第一行两个空格隔开的正整数n和d,分别表示关数和相邻僵尸间的距离。接下来n行每行两个空格隔开的正整数,第i + 1行为Ai和 Xi,分别表示相比上一关在僵尸队列排头增加血量为Ai 点的僵尸,排头僵尸从距离房子Xi米处开始接近。
输出格式:
一个数,n关植物攻击力的最小总和 ,保留到整数。
由题意,前面的僵尸死了之后,后面的僵尸才能受到伤害,带着这样的时间限制是很难处理的。为了去掉这个限制,可以把后面僵尸的血量加上前面僵尸的血量总和,这样的处理与原题意等价
这样问题就转化成了:有很多僵尸,它们从一开始就受到持续伤害,并且在到达房子之前死掉,询问这个持续伤害的最小值。很明显答案为: ans[i]=max(血量距离)=max(sum[i]−sum[j−1]x[i]+i∗d−j∗d) , sum 数组表示血量前缀和
但是 N 规模 1e5 ,直接枚举肯定T的稳稳的
这时候就要靠智商思维了。注意到 sum[i]−sum[j−1]x[i]+i∗d−j∗d 与斜率形式很相似,我们把 ( x[i]+i∗d,sum[i] ) 作为 P 点,而 (sum[j−1],j∗d) 作为 Qj 点,那么答案就是 P 点到所有 Qj 点斜率的最大值。随着 j 的增加, Qj 的横纵坐标都增加, P 点也是一样的,因此这个斜率最大值可以用凸包维护三分查找,可以自己在纸上画个图看看
这道题需要注意细节…最好想清楚再写
#include
#include
#include
using namespace std ;
int N , topp ;
long long d , A[100005] , X[100005] , sum[100005] ;
struct Vector{
double x , y ;
Vector(){} ;
Vector( double x_ , double y_ ):
x(x_) , y(y_){} ;
} ;
typedef Vector Point ;
typedef Vector Vv ;
Vv operator - ( const Vv &A , const Vv &B ){ return Vector( A.x - B.x , A.y - B.y ) ;}
double Cross( const Vv &A , const Vv &B ){ return A.x * B.y - A.y * B.x ; }
Point p[100005] , a[100005] ;
double K( int Q , int P ){
return 1.0 * ( sum[P] - p[Q].y ) / ( X[P] + a[P].x - p[Q].x ) ;
}
double calcu( int now ){
int lf = 1 , rg = topp ;
double rt = 0 ;
while( rg - lf >= 3 ){
int Lmid = lf + ( rg - lf ) / 3 , Rmid = rg - ( rg - lf ) / 3 ;
if( K( Lmid , now ) < K( Rmid , now ) )
lf = Lmid ;
else rg = Rmid ;
}
for( int i = lf ; i <= rg ; i ++ )
rt = max( rt , K( i , now ) ) ;
return rt ;
}
void solve(){
double ans = 0 ;
topp = 0 ;
for( int i = 1 ; i <= N ; i ++ ){
while( topp >= 2 && Cross( a[i] - p[topp-1] , p[topp] - p[topp-1] ) >= 0 ) topp -- ;
p[++topp] = a[i] ;
ans += calcu( i ) ;
}
printf( "%.0f" , ans ) ;
}
int main(){
scanf( "%d%lld" , &N , &d ) ;
for( int i = 1 ; i <= N ; i ++ ){
scanf( "%lld%lld" , &A[i] , &X[i] ) ;
sum[i] = sum[i-1] + A[i] ;
a[i] = Point( 1.0 * d * i , 1.0 * sum[i-1] ) ;
}
solve() ;
}