【题解】 SOJ 嘟嘟可故事集(斜率优化/我不玩元神!!!)
搞了一晚上的博弈论,脑子都要炸了,来搞点轻松的
先申明:笔者不玩元神。本题是老师出的。
以下是原题:
在夏天的金苹果岛活动中,可以免费兑换可莉的四星专武——嘟嘟可故事集。这件武器会提供一个增伤 buff,初始值为 \(0\),效果如下。
假设可莉将进行 \(N\) 次攻击,第 \(i\) 次攻击的基础攻击力为 \(a_i\)。在进行攻击前,这一数值会累加到武器的 buff 值中。如果选择普通攻击,则此轮攻击会造成 \(a_i\) 点伤害。若选择重击,则会在造成 \(a_i\) 点伤害的基础上,附加武器当前的 buff 值乘以 \(a_i\) 的额外伤害,并清空武器的 buff 值(重置为 \(0\))。
由于体力的限制,可莉两次重击之间的轮次差值至少为 \(D\)(如果第 \(i\) 轮与第 \(j\) 轮使用重击,则需满足 \(|i-j|\geq D\))。
问如何规划可莉每次的攻击方式,可以造成最高的伤害总量。输出这一最大值。
数据范围:\(1\leq N \leq 10^5,\ 1\leq D \leq N,\ 1\leq a_i \leq 10^5\)
显然无论如何攻击都会有基础攻击力 \(\sum a_i\),故我们只需去决策重击的方案。
记 \(f_i\) 表示最后一次重击以第 \(i\) 次攻击为末尾,的最大攻击力。用 \(s_i\) 表示攻击力 \(a_i\) 的前缀和。
有
最终答案为 \(\sum a_i +\max f_i\) 。朴素做法为 \(O(n^2)\),
考虑优化 \(otherwise\) 情况,整理一下式子,
把仅与 \(i\) 有关的提到式子左边,仅与 \(j\) 相关、同时包含\(i\) 、\(j\)的因式留在右边,
由斜率式 \(b=y-kx\) 可以得到
由于是求最大值,我们可以用单调栈维护平面内表示状态的点的上凸壳。
斜率 \(k\) 并不单调,可以在单调栈上二分。每次转移后,将 \(i-D+1\) 入栈。复杂度优化到了 \(O(n \log n)\)。
注意:比较斜率时用浮点比较,乘法比较分数会炸 long long。
Code:
//Coder: HyperSQ.
//Problem from Sicily Online Judge.
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
const long double eps=1e-10;
int n,D;
ll a[maxn],s[maxn],f[maxn];
int stk[maxn],top;
ll x(int i){return s[i];}
ll y(int i){return f[i];}
ll k(int i){return a[i];}
bool ltk(ll now,ll k){
ll x_1=x(stk[now]),y_1=y(stk[now]);
ll x_2=x(stk[now+1]),y_2=y(stk[now+1]);
long double k1=(long double)(y_2-y_1)/(long double)(x_2-x_1);
return k1-(long double)k<=eps;
}
bool olf(ll x_0,ll y_0){
ll x_1=x(stk[top-1]),y_1=y(stk[top-1]);
ll x_2=x(stk[top]),y_2=y(stk[top]);
long double k1=(long double)(y_0-y_2)/(long double)(x_0-x_2);
long double k2=(long double)(y_2-y_1)/(long double)(x_2-x_1);
return k1-k2>=-eps;
}
int main(){
freopen("dodoco.in","r",stdin);
freopen("dodoco.out","w",stdout);
scanf("%d%d",&n,&D);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
s[i]=s[i-1]+a[i];
}
top=1;
for(int i=1;i<=n;i++){
if(i-D<0){
f[i]=s[i]*a[i];
continue;
}
ll l=1,r=top;
while(l<r){
ll mid=(l+r)/2;
if(ltk(mid,k(i))) r=mid;
else l=mid+1;
}
int j=stk[l];
f[i]=f[j]+a[i]*(s[i]-s[j]);
while(top>1&&olf(x(i-D+1),y(i-D+1))) top--;
stk[++top]=i-D+1;
}
ll ans=0;
for(int i=1;i<=n;i++){
assert(f[i]>=0);
ans=max(f[i],ans);
}
printf("%lld",ans+s[n]);
}

浙公网安备 33010602011771号