【题解】 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\) 的前缀和。

\[f_i=\begin{cases} s_ia_i &i-D<0 \\ \max \{f_j+a_i(s_i-s_j)\} \ ,j\in [0,i-D] &otherwise \end{cases} \]

最终答案为 \(\sum a_i +\max f_i\) 。朴素做法为 \(O(n^2)\)

考虑优化 \(otherwise\) 情况,整理一下式子,

\[f_i=f_j+a_is_i-a_is_j \]

把仅与 \(i\) 有关的提到式子左边,仅与 \(j\) 相关、同时包含\(i\)\(j\)的因式留在右边,

\[f_i-a_is_i=f_j-a_is_j \]

由斜率式 \(b=y-kx\) 可以得到

\[\begin{cases} b=f_i-a_is_i\\ x=s_j\\ k=a_i\\ y=f_j \end{cases} \]

由于是求最大值,我们可以用单调栈维护平面内表示状态的点的上凸壳。

斜率 \(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]);
}
posted @ 2021-12-17 23:45  HyperSQ  阅读(17)  评论(0)    收藏  举报