题面
时间限制:1s,空间限制:1024MB
题目描述
手拿咒刃砍金门,众神直呼不是人
椅子玩自定义咒刃,一路打到了第三关的银行,祂想在黑色大桥无伤看守者之前找点刺激的。
加了模组的银行极大。具体地说,这一关有 n n n 个战斗场景按顺序排成一行,依次编号为 1 ∼ n 1\sim n 1∼n ,椅子一开始在第一个战斗场景,而黑色大桥在第 n n n 个场景之后。
每个场景在开头有个金门,每个金门里有一把咒刃,每个场景都有一只怪(细胞三爹:拳皇,鸡哥,三刀哥)。
第 i i i 个场景的咒刃有四个属性:第一刀伤害 b i b_i bi,后摇 k i k_i ki ,加速条件杀敌数 a i a_i ai ,加速范围 d i d_i di 。
由于上一关的咒刃等级太低,所以椅子必须得砍第一个金门拿到第一把咒刃。除此之外,椅子在后面的任意场景开头都可以砍金门,获得新的咒刃,并用那把咒刃直到下一次砍金门或者到达黑色大桥。
刚拿到第 i i i 个场景的咒刃时,第一刀会非常刺激,产生 b i b_i bi 刺激度,但后摇会降低刺激度 k i k_i ki,直到开始加速。当拿这把刀杀敌数达到 a i − d i a_i-d_i ai−di 时,刺激度会呈朝下的抛物线(二次项系数为 1 1 1)变化,杀敌数 a i a_i ai 时到达顶点,此后刺激度开始降低。一直到杀敌数 a i + d i a_i+d_i ai+di 时,由于长时间保持无伤打怪,刺激度随着疫病上来了,此后又呈二次函数上升……
具体地说,如果带上第
l
l
l 个金门的咒刃一直打到第
r
r
r 场景结束,将会产生
f
l
(
r
−
l
+
1
)
f_l(r-l+1)
fl(r−l+1) 的刺激度。函数
f
i
(
x
)
f_i(x)
fi(x) 定义如下:
f
i
(
x
)
=
{
−
k
i
x
+
b
i
(
x
≤
a
i
−
d
i
)
−
k
i
(
a
i
−
d
i
)
+
b
i
+
d
i
2
−
(
a
i
−
x
)
2
(
a
i
−
d
i
<
x
<
a
i
+
d
i
)
−
k
i
(
a
i
−
d
i
)
+
b
i
+
(
x
−
(
a
i
+
d
i
)
)
2
(
x
≥
a
i
+
d
i
)
f_i(x)=\begin{cases} -k_ix+b_i~~&~(x\leq a_i-d_i)\\ -k_i(a_i-d_i)+b_i+d_i^2-(a_i-x)^2~~&~(a_i-d_i<x<a_i+d_i)\\ -k_i(a_i-d_i)+b_i+(x-(a_i+d_i))^2~~&~(x\geq a_i+d_i) \end{cases}
fi(x)=⎩⎪⎨⎪⎧−kix+bi −ki(ai−di)+bi+di2−(ai−x)2 −ki(ai−di)+bi+(x−(ai+di))2 (x≤ai−di) (ai−di<x<ai+di) (x≥ai+di)
形状类似下图:
开了新的金门后,先前的刺激度会停止变化,随后刺激度将加上新的咒刃带来的刺激度。也就是说,把每一把咒刃带来的刺激度累加就是总刺激度。
椅子希望找到一种合适的砍金门方案,使得最终总刺激度最大。但是祂正在直播,所以把这个问题交给了你。
请输出最大总刺激度。
输入格式
第一行一个数字 n n n ,后面 n n n 行,每行四个数字 k i , a i , b i , d i k_i,a_i,b_i,d_i ki,ai,bi,di ,如题意。每个数字之间用单个空格分隔。
n n n
k 1 a 1 b 1 d 1 k_1~a_1~b_1~d_1 k1 a1 b1 d1
k 2 a 2 b 2 d 2 k_2~a_2~b_2~d_2 k2 a2 b2 d2
. . . ... ...
k n a n b n d n k_n~a_n~b_n~d_n kn an bn dn
输出格式
一行一个数字,表示最大总刺激度。
样例输入
#1
5
1 2 8 1
9 3 8 2
4 3 3 3
4 1 2 1
7 2 1 2
#2
4
1 4 1 4
1 4 2 4
1 4 1 4
1 4 3 4
#3
4
2 1 8 1
5 2 1 2
3 1 1 1
1 2 1 2
样例输出
#1
23
#2
35
#3
19
提示
#1
依次拿第 1 , 3 , 5 1,3,5 1,3,5 把咒刃,可以获得最大刺激度 8 + 11 + 4 = 23 8+11+4=23 8+11+4=23 。
#2
符合数据 6~10 的特殊限制
#3
符合数据 16~20 的特殊限制
数据范围
对于所有数据, 1 ≤ k i , b i ≤ 1 0 6 , 1 ≤ d i ≤ a i ≤ n ≤ 1 0 6 1\leq k_i,b_i\leq 10^6,1\leq d_i\leq a_i\leq n\leq 10^6 1≤ki,bi≤106,1≤di≤ai≤n≤106 .
测试点编号 | n ≤ n\leq n≤ | 特殊限制 |
---|---|---|
1~5 | 5 × 1 0 3 5\times10^3 5×103 | 无 |
6~10 | 1 0 6 10^6 106 | a i = d i = n a_i=d_i=n ai=di=n |
11~15 | 1 0 5 10^5 105 | 无 |
16~20 | 1 0 6 10^6 106 | a i + d i ≥ n a_i+d_i\geq n ai+di≥n |
21~25 | 1 0 6 10^6 106 | 无 |
暖心提醒:本题的输入量较大(甚至用 std::scanf()
都会有小概率卡常),请使用较快的读入方式。
题解
法 1
通用解法
60pts
观察这个函数的三个部分
- 直线
- (二次项系数均为 -1 的)抛物线
- (二次项系数均为 1 的)抛物线
当然,为了 DP 转移,我们得在常数项上加上左边的 DP 值。
我们发现,这三者都可以单独用李超线段树维护最大值。
李超线段树可以合理维护的函数,只需要满足两个函数最多只有一个交点(或者延伸至无穷的一段区间)。
所以,每次在特定区间添加函数,用李超线段树维护单点最大值,时间复杂度 O ( n log 2 n ) O(n\log^2n) O(nlog2n) 。
80 pts
或许并不需要限制函数的作用区间。
比如说前两个函数,由于直线的斜率一定为负,所以在 x ≤ a i − d i x\leq a_i-d_i x≤ai−di 时,直线本就比抛物线更优。同理,抛物线在 x ∈ ( a i − d i , a i + d i ) x\in(a_i-d_i,a_i+d_i) x∈(ai−di,ai+di) 时比直线更优。
如果只有这前两个函数,就可以直接添加进全区间,复杂度就保证是 O ( n log n ) O(n\log n) O(nlogn) 。
100 pts
我们看第三个函数,一个向上的抛物线,把它补全的话,前面一定比前两个函数高了。
但是我们可以人工构造一个分段函数。
C
i
(
x
)
=
{
−
k
i
(
a
i
−
d
i
)
+
b
i
(
x
<
a
i
+
d
i
)
−
k
i
(
a
i
−
d
i
)
+
b
i
+
(
x
−
(
a
i
+
d
i
)
)
2
(
x
≥
a
i
+
d
i
)
C_i(x)=\begin{cases} -k_i(a_i-d_i)+b_i &~(x<a_i+d_i)\\ -k_i(a_i-d_i)+b_i+(x-(a_i+d_i))^2~~&~(x\geq a_i+d_i) \end{cases}
Ci(x)={−ki(ai−di)+bi−ki(ai−di)+bi+(x−(ai+di))2 (x<ai+di) (x≥ai+di)
而这个函数是可以用李超树维护的。
当然,这个分段函数怎么构造都行,满足条件就够了,比如 std 就是添加了一条斜率为 1 的直线(定义在整数域上的该二次函数可以认为底部导数为 1 )。
所以,令
A
i
(
x
)
=
−
k
i
x
+
b
i
,
B
i
(
x
)
=
−
k
i
(
a
i
−
d
i
)
+
b
i
+
d
i
2
−
(
a
i
−
x
)
2
A_i(x)=-k_ix+b_i~~,~~B_i(x)=-k_i(a_i-d_i)+b_i+d_i^2-(a_i-x)^2
Ai(x)=−kix+bi , Bi(x)=−ki(ai−di)+bi+di2−(ai−x)2
那么 f i ( x ) = max { A i ( x ) , B i ( x ) , C i ( x ) } f_i(x)=\max\{A_i(x),B_i(x),C_i(x)\} fi(x)=max{Ai(x),Bi(x),Ci(x)} 。
DP 的转移式就可以变成
d
p
[
i
]
=
max
j
<
i
{
d
p
[
j
]
+
A
j
+
1
′
(
i
)
,
d
p
[
j
]
+
B
j
+
1
′
(
i
)
,
d
p
[
j
]
+
C
j
+
1
′
(
i
)
}
dp[i]=\max_{j<i}\{dp[j]+A'_{j+1}(i),dp[j]+B'_{j+1}(i),dp[j]+C'_{j+1}(i)\}
dp[i]=j<imax{dp[j]+Aj+1′(i),dp[j]+Bj+1′(i),dp[j]+Cj+1′(i)}
我们单独维护三种函数的“凸包”,时间复杂度 O ( n log n ) O(n\log n) O(nlogn) 。
法 2
观察到 ( r − l + 1 ) 2 = r 2 + ( l − 1 ) 2 − 2 ( l − 1 ) r (r-l+1)^2=r^2+(l-1)^2-2(l-1)r (r−l+1)2=r2+(l−1)2−2(l−1)r ,我们把 r 2 r^2 r2 甩到 max { } \max\{\} max{} 外面,可以把二次项都除掉,它就变成了一个一次函数!
于是我们直接拿三个李超树维护这三段一次函数,
由于第三段直接加会影响到前两段,但是第三段的作用区间是 [ l − 1 + a l + d l , n ] [l-1+a_l+d_l,n] [l−1+al+dl,n] ,而我们做DP是从左到右的,所以我们可以在 l − 1 + a l + d l l-1+a_l+d_l l−1+al+dl 处打个标记,扫到了那里再加第三段。
时间复杂度 O ( n log n ) O(n\log n) O(nlogn) ,各方面吊打标算。
CODE(std)
#include<map>
#include<set>
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<random>
#include<bitset>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<unordered_map>
#pragma GCC optimize(2)
using namespace std;
#define MAXN 1000005
#define LL long long
#define ULL unsigned long long
#define ENDL putchar('\n')
#define DB double
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
#define PR pair<LL,int>
int xchar() {
static const int maxn = 1000000;
static char b[maxn];
static int pos = 0,len = 0;
if(pos == len) pos = 0,len = fread(b,1,maxn,stdin);
if(pos == len) return -1;
return b[pos ++];
}
#define getchar() xchar()
LL read() {
LL f = 1,x = 0;int s = getchar();
while(s < '0' || s > '9') {if(s<0)return -1;if(s=='-')f=-f;s = getchar();}
while(s >= '0' && s <= '9') {x = (x<<1) + (x<<3) + (s^48);s = getchar();}
return f*x;
}
void putpos(LL x) {if(!x)return ;putpos(x/10);putchar((x%10)^48);}
void putnum(LL x) {
if(!x) {putchar('0');return ;}
if(x<0) putchar('-'),x = -x;
return putpos(x);
}
void AIput(LL x,int c) {putnum(x);putchar(c);}
int n,m,s,o,k;
int a[MAXN],b[MAXN],ki[MAXN],d[MAXN];
inline LL Max(LL a,LL b) {return a>b ? a:b;}
struct it{
LL a,b,c;
LL k,d;
int ad;
it(){a=b=k=ad=0;c=d=-5e18;}
LL F(int x) {
if(x <= ad) return k*x+d;
return a*x*x + b*x + c;
}
}tre[MAXN*6];
int ls[MAXN*6],rs[MAXN*6],cnt;
void addtree(int &a,int al,int ar,it y) {
if(!a) tre[a=++cnt] = it(),ls[cnt]=rs[cnt]=0;
LL l1 = tre[a].F(al),r1 = tre[a].F(ar);
LL l2 = y.F(al),r2 = y.F(ar);
if(l1 >= l2 && r1 >= r2) return ;
if(l1 <= l2 && r1 <= r2) {tre[a]=y;return ;}
int md = (al + ar) >> 1;
if(tre[a].F(md) <= y.F(md)) swap(tre[a],y),swap(l1,l2);
if(l1 >= l2) addtree(rs[a],md+1,ar,y);
else addtree(ls[a],al,md,y);
return ;
}
LL findtree(int a,int x,int al,int ar) {
if(al > x || ar < x || !a) return (LL)-5e18;
LL res = tre[a].F(x); int md = (al + ar) >> 1;
if(al == ar) return res;
return Max(res,Max(findtree(ls[a],x,al,md),findtree(rs[a],x,md+1,ar)));
}
LL dp[MAXN];
int main() {
freopen("blackbridge.in","r",stdin);
freopen("blackbridge.out","w",stdout);
n = read();
dp[0] = 0;
int r1 = 0,r2 = 0,r3 = 0;
for(int i = 1;i <= n;i ++) {
ki[i] = read();
a[i] = read(); b[i] = read(); d[i] = read();
it s1,s2,s3;
s1.k = -ki[i]; s1.d = dp[i-1] + b[i] + ki[i]*1ll*(i-1);
s1.ad = n+1;
s2.a = -1; s2.ad = 0;
s2.c = -ki[i]*1ll*(a[i]-d[i]) + b[i] + dp[i-1] + d[i]*1ll*d[i] - (a[i]+i-1)*1ll*(a[i]+i-1);
s2.b = (a[i]+i-1)*2;
s3.a = 1; s3.ad = a[i] + d[i] + i-1;
s3.b = -2ll*(a[i]+d[i]+i-1);
s3.c = -ki[i]*1ll*(a[i]-d[i]) + b[i] + dp[i-1] + s3.ad*1ll*s3.ad;
s3.d = -ki[i]*1ll*(a[i]-d[i]) + b[i] - s3.ad + dp[i-1];
s3.k = 1;
addtree(r1,1,n,s1);addtree(r2,1,n,s2);addtree(r3,1,n,s3);
dp[i] = Max(Max(findtree(r1,i,1,n),findtree(r2,i,1,n)),findtree(r3,i,1,n));
}
AIput(dp[n],'\n');
return 0;
}