UOJ187[UR#13]Ernd(斜率优化DP)

#187. 【UR #13】Ernd

这里是跳蚤国中央广播电台,现在为您转播的是著名人类智慧大师 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 单调递增

输出格式

输出一行一个正整数,表示这场赛事中双方可以拿到的最高分。

样例一

input

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

容易证明这是得分最高的方案。

样例二

input

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 n20 n ≤ 20
2 10 n1000 n ≤ 1000
3 20 n105 n ≤ 10 5 ai5 a i ≤ 5
4 20 n5×105 n ≤ 5 × 10 5 ,答案不超过 104 10 4
5 40 n5×105 n ≤ 5 × 10 5

对于所有数据, 1n5×105 1 ≤ n ≤ 5 × 10 5 1ai,bi109 1 ≤ a i , b i ≤ 10 9

时间限制: 2s 2 s

空间限制: 128MB 128 MB

分析&&思路:

    P.S. 这题看了看题解,发现其实挺巧妙的,只是自己想的时候没想到……(还是太菜了)
    以下为UOJ原题题解:

算法二

对于subtask2, n1000 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,fj1+(ij+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 ∘ ] 范围的方向上。

旋转坐标系,将每个点变为 (biai,bi+ai) ( b i − a i , b i + a i ) ,那么接到一个果子后,能接到的其他果子都在这个点的右上方。

问题变成了一个二维偏序问题,用树状数组维护上升子序列即可。

观察subtask4, n5×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 分。

算法四

现在开始考虑如何优化第二部分转移。

注意到一段内的元素在按照 biai b i − a i 排序后可能不连续,但相对顺序不会改变,因此我们不用担心转移顺序的问题。

这部分转移每段之间彼此独立,因此我们考虑对于每段内部分别计算。

观察转移方程 fi=max{fj1+(ij+1)2} f i = max { f j − 1 + ( i − j + 1 ) 2 } ,设 P=fj1+(ij+1)2 P = f j − 1 + ( i − j + 1 ) 2 ,那么有:

(fj+j22j)=2i×j+(Pi22i) ( f j + j 2 − 2 j ) = 2 i × j + ( P − i 2 − 2 i )

我们发现这个式子可以斜率优化,将每个决策点 j j 化为平面上的点 (j,fj+j22j) ( 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);}
/*================Header Template==============*/
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;
}

你可能感兴趣的:(DP,树状数组)