这里是跳蚤国中央广播电台,现在为您转播的是著名人类智慧大师 picks 博士与人工智能 betacome 之间的第二轮赛事。
这一场交锋的规则由网友 Po***QQ 提供,这位网友也将获得由不想跳的跳蚤不是好跳蚤——最强跳蚤跳跳跳公司提供的金牌跳蚤一只。
在刚刚结束的第一轮比赛中,因为 picks 博士在关键时刻出现了失误,他惜败给了 betacome。众所周知,A 先生在比赛前夕接受采访时,曾经说过相信 picks 博士可以以全胜的战绩战胜 betacome,请问 A 先生您现在怎么看?
“我仍然觉得 picks 博士的赢面要大一点,第一轮可能是因为他没有调整好心态,在比赛最后的水平堪比业余选手 m**o,这是非常反常的,如果他能够避免这样的失误的话,我觉得仍然觉得人类智慧可以取得最终的胜利。”
好,现在,我们可以看到,第二轮的比赛已经开始了,A 先生您能给我们简要介绍一下这一轮比赛的规则吗?
“好的,相信大家都玩过 osu 吧,这一场的规则来自 osu 的 ctb 模式。其实本来的规则是其他的模式,但是在 betacome 展示了它鬼畜的手速之后,经过比赛委员会的紧急讨论,决定把规则临时改成 ctb 模式。”
“简要来讲,你有一个盘子,时刻 0 0 时你可以把盘子放到数轴上的任何一个整数坐标处,接着每一时刻你可以把盘子往左移动一个单位、往右移动一个单位或者不动。接着系统生成了 n n 个水果,第 i i 个水果会在第 bi b i 时刻出现在 ai a i 坐标处,保证 bi b i 单调递增。如果你的盘子在 bi b i 时刻恰好出现在 ai a i 处,那么就能接到这个水果。”
“最后一个水果落下后,游戏结束。分数用这个方式计算,定义变量 K K ,最开始 K K 是 0 0 ,按照顺序考虑每一个水果,如果你接到了当前水果,那么把 K K 加一,否则你的得分将加上 K2 K 2 并将 K K 清零,游戏结束后你的得分还会加上游戏结束时的 K2 K 2 。即你的得分是每一段连续接到水果的极长区间的长度平方和。”
好的,谢谢 A 先生,看来这场赛事的规则还是非常有趣的呀。现在我们节目组已经拿到了这场赛事中使用的水果信息(即数组 a a 与数组 b b ),A 先生您能帮我们计算双方可以拿到的最高分吗?
“当然,我们可以非常容易的算出来理论的最高分是****。” 出乎意料地,AcrossTheSky 居然一下子就说出了答案。
了解 AcrossTheSky 真实智力的你觉得这非常反常,你有理由怀疑 AcrossTheSky 是随便报了一个数字出来。所以你决定亲自计算一下答案,来检验一下 AcrossTheSky 是不是在瞎 BB。
输入格式
第一行一个正整数 n n ,表示水果的个数。
接下来 n n 行,第 i i 行包含两个空格分隔的正整数 ai,bi a i , b i ,表示第 i i 个水果会在第 bi b i 时刻出现在 ai a i 坐标处,保证 bi b i 单调递增。
输出格式
输出一行一个正整数,表示这场赛事中双方可以拿到的最高分。
样例一
5
2 1
8 2
10 3
5 6
5 8
output
5
explanation
接住第 1,4,5 1 , 4 , 5 或者第 2,4,5 2 , 4 , 5 个水果,得分为 12+22=5 1 2 + 2 2 = 5 。
容易证明这是得分最高的方案。
样例二
10
5 1
6 2
10 5
2 6
10 7
4 17
9 19
9 24
8 25
1 28
output
14
explanation
一种得到最高分的方案是,接住第 1,2,5,7,8,9 1 , 2 , 5 , 7 , 8 , 9 个果子,总得分为 22+12+32=14 2 2 + 1 2 + 3 2 = 14 。
样例三
见样例数据下载。这个数据中 n=1000 n = 1000 。
限制与约定
由于一些原因,本题使用捆绑测试。每个子任务有若干个测试点,分为 5 5 个子任务,你只有通过一个子任务的所有测试点才能得到这个子任务的分数。
子任务 |
分值 |
限制 |
1 |
10 |
n≤20 n ≤ 20 |
2 |
10 |
n≤1000 n ≤ 1000 |
3 |
20 |
n≤105 n ≤ 10 5 , ai≤5 a i ≤ 5 |
4 |
20 |
n≤5×105 n ≤ 5 × 10 5 ,答案不超过 104 10 4 |
5 |
40 |
n≤5×105 n ≤ 5 × 10 5 |
对于所有数据, 1≤n≤5×105 1 ≤ n ≤ 5 × 10 5 , 1≤ai,bi≤109 1 ≤ a i , b i ≤ 10 9
时间限制: 2s 2 s
空间限制: 128MB 128 MB
分析&&思路:
P.S. 这题看了看题解,发现其实挺巧妙的,只是自己想的时候没想到……(还是太菜了)
以下为UOJ原题题解:
算法二
对于subtask2, n≤1000 n ≤ 1000
我们定义“段”为满足对于其中任意相邻元素 i i 和 i+1 i + 1 ,满足接到第 i i 个水果后可以接到第 i+1 i + 1 个水果的极长子区间。
考虑暴力dp,定义 fi f i 为以第 i i 个水果结尾的接水果序列的最大总得分,那么有:
fi=max(fi,fj+1| 接到第 j 个水果后可以接到第 i 个水果) f i = max ( f i , f j + 1 | 接到第 j 个水果后可以接到第 i 个水果 )
fi=max(fi,fj−1+(i−j+1)2| 第 i 个水果和第 j 个水果属于同一段) f i = max ( f i , f j − 1 + ( i − j + 1 ) 2 | 第 i 个水果和第 j 个水果属于同一段 )
枚举 j j 进行转移,时间复杂度 O(n2) O ( n 2 ) ,期望得分 20 20 分。
算法三
我们考虑如何优化第一部分转移。
我们将水果都放在二维平面上,第 i i 个水果变成一个坐标为 (ai,bi) ( a i , b i ) 的点,那么容易发现,接到一个果子后,能接到的其他果子都在这个点的 [45∘,135∘] [ 45 ∘ , 135 ∘ ] 范围的方向上。
旋转坐标系,将每个点变为 (bi−ai,bi+ai) ( b i − a i , b i + a i ) ,那么接到一个果子后,能接到的其他果子都在这个点的右上方。
问题变成了一个二维偏序问题,用树状数组维护上升子序列即可。
观察subtask4, n≤5×105 n ≤ 5 × 10 5 ,答案不超过 104 10 4 ,这意味着任意一段的长度都不会超过 104−−−√=100 10 4 = 100 。
第一部分转移使用算法三,第二部分转移暴力,时间复杂度 O(nlogn+100×n) O ( n log n + 100 × n ) ,期望得分 40 40 分。
算法四
现在开始考虑如何优化第二部分转移。
注意到一段内的元素在按照 bi−ai b i − a i 排序后可能不连续,但相对顺序不会改变,因此我们不用担心转移顺序的问题。
这部分转移每段之间彼此独立,因此我们考虑对于每段内部分别计算。
观察转移方程 fi=max{fj−1+(i−j+1)2} f i = max { f j − 1 + ( i − j + 1 ) 2 } ,设 P=fj−1+(i−j+1)2 P = f j − 1 + ( i − j + 1 ) 2 ,那么有:
(fj+j2−2j)=2i×j+(P−i2−2i) ( f j + j 2 − 2 j ) = 2 i × j + ( P − i 2 − 2 i )
我们发现这个式子可以斜率优化,将每个决策点 j j 化为平面上的点 (j,fj+j2−2j) ( j , f j + j 2 − 2 j ) ,维护一个上凸包即可。
注意到我们维护的是上凸包而查询斜率 2i 2 i 随 i i 单调递增,因此我们需要用一个栈而不是队列来维护这个凸包。
当然你要二分斜率也是完全可以的。
时间复杂度 O(nlogn) O ( n log n ) ,期望可以通过全部测试点拿到 100 100 分。
当时想这题的时候已经想到了用DP来求解,但是一直没有想到怎么来进行优化..可能看到这种题的思路还是不够清晰吧..以后还要再多加练习。
Code
#pragma GCC optimize(3)
#include
using namespace std;
typedef long long ll;
bool Finish_read;
template<class T>inline void read(T &x){Finish_read=0;x=0;int f=1;char ch=getchar();while(!isdigit(ch)){if(ch=='-')f=-1;if(ch==EOF)return;ch=getchar();}while(isdigit(ch))x=x*10+ch-'0',ch=getchar();x*=f;Finish_read=1;}
template<class T>inline void print(T x){if(x/10!=0)print(x/10);putchar(x%10+'0');}
template<class T>inline void writeln(T x){if(x<0)putchar('-');x=abs(x);print(x);putchar('\n');}
template<class T>inline void write(T x){if(x<0)putchar('-');x=abs(x);print(x);}
const int maxn=500005;
int n,m,pre[maxn],y[maxn];
ll c[maxn],f[maxn],ans;
bool can[maxn];
struct node {
int x,y,id;
node(int _x=0,int _y=0,int _id=0):x(_x),y(_y),id(_id){}
inline bool operator < (const node &rhs) const {
return x==rhs.x?yinline ll lb(ll x) {
return x&-x;
}
inline void add(int pos,ll d) {
for(;pos<=m;pos+=lb(pos))
c[pos]=max(c[pos],d);
}
inline ll query(int pos) {
ll res=0;
for(;pos;pos-=lb(pos))
res=max(res,c[pos]);
return res;
}
int main() {
read(n);
for(int i=1,u,v;i<=n;i++) {
read(u),read(v);
p[i]=node(v-u,v+u,i);
y[i]=p[i].y;
if(i>1&&p[i].x>=p[i-1].x&&p[i].y>=p[i-1].y)
can[i]=1;
}
sort(y+1,y+n+1);
m=unique(y+1,y+n+1)-y-1;
for(int i=1;i<=n;i++)
p[i].y=lower_bound(y+1,y+m+1,p[i].y)-y;
sort(p+1,p+n+1);
for(int i=1;i<=n;i++) {
ll now=p[i].id,res=query(p[i].y);
f[now]=res+(now-1)*(now-1);
res++;
if(can[now]) {
int j=now-1;
while(pre[j]&&f[j]-f[pre[j]]<=2*now*(j-pre[j]))
j=pre[j];
res=max(res,f[j]-2*now*(j-1)+now*now);
while(pre[j]&&(now-j)*(f[j]-f[pre[j]])<=(j-pre[j])*(f[now]-f[j]))
j=pre[j];
pre[now]=j;
}
add(p[i].y,res);
ans=max(ans,res);
}
printf("%lld\n",ans);
return 0;
}