@loj - [email protected]「CEOI2017」Building Bridges

目录

  • @[email protected]
  • @[email protected]
  • @accepted [email protected]
  • @[email protected]

@[email protected]

有 n 根柱子依次排列,第 i 根柱子的高度为 hi 。现可以花费 (hi - hj)^2 的代价建桥架在第 i 根柱子和第 j 根柱子之间。

所有用不到的柱子都会被拆除,第 i 根柱子被拆除的代价为 wi 。

求用桥把第 1 根柱子和第 n 根柱子连接的最小代价。注意桥梁不能在端点以外的任何地方相交。

input

第一行一个正整数 n。 2 <= n <= 10^5。

第二行 n 个空格隔开的整数,依次表示 h1, h2, ..., hn。0 <= hi <= 10^6。

第三行 n 个空格隔开的整数,依次表示 w1, w2, ..., wn。0 <= |wi| <= 10^6。

output

输出一个整数表示最小代价,注意最小代价不一定是正数。

sample input

6

3 8 7 1 6 6

0 -1 9 1 2 0

sample output

17

@[email protected]

一个很 naive 的 dp:定义状态 \(dp[i]\) 表示将 1 与 i 连接起来的最小费用,并再定义一个前缀和 \(s[i] = \sum_{p=1}^{i}w[p]\),则状态转移为:

\[dp[i]=min\{dp[j]+s[i]-s[j]+(h[i]-h[j])^2\}\]

满脸的斜率优化。

横坐标为 \(x[j] = h[j]\),纵坐标为\(y[j] = dp[j] - s[j] + h[j]^2\),斜率为 \(k[i] = 2*h[i]\),只和 i 有关的常数 \(c[i] = s[i] + h[i]^2\)。

转移式变为:

\[dp[i]=min\{c[i]+y[j]-k[i]*x[j]\}\]

然而……斜率不单调就算了……TM 横坐标也不单调。

对于这种题,一是写平衡树,一是用 cdq 分治。

因为我这辈子都不会去写平衡树维护斜率的 cdq 分治非常的优秀,所以我就在这里讲一下 cdq。

感性描述一下我们的思想:我们把区间分为两部分,左半部分依照横坐标排序,右半部分依照斜率排序,同时保证左半部分所有的编号小于右半部分所有的编号。

在这个前提下,用左边去更新右边,就是一个简单的单调栈问题了。

我们当然不可能在每一层都去排一下序什么的,这样时间复杂度就退化成 O(nlog^2n) 的。

所以我们的解决方法是这样的:

首先我们把所有点按照斜率来排序,开始递归区间 [1, n]。

对于当前这一层 [l, r],将这些点按照编号与 mid 的关系,分成左右两部分,同时两部分内部都保持斜率单调的顺序。因为我们一开始递归的是 [1, n],按照上面这一套方法,递归 [l, r] 的时候这个区间内所有点的编号都在 [l, r] 范围内。

然后,先递归 [l, mid],求出这段区间的 dp 值,并在递归时以它们的横坐标为关键字进行排序(归并排序)。

再一套单调栈更新右半部分。递归 [mid, r] 求解。此时左右两部分都是以横坐标为关键字的有序状态。

在最后归并即可。

好像有些冗长……最好看一看代码确认一下细节。

@accepted [email protected]

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 100000;
const ll INF = (1LL<<62);
struct node{
    ll w, h, c, k, x, y, dp;
    int pos;
}a[MAXN + 5], tmp[MAXN + 5], que[MAXN + 5];
bool cmp(node a, node b) {
    return a.k < b.k;
}
void cdq(int le, int ri) {
    if( le == ri ) {
        a[le].x = a[le].h;
        a[le].y = a[le].h*a[le].h + a[le].dp - a[le].w;
        return ;
    }
    int mid = (le + ri) >> 1, p = le, q = mid + 1, r = le;
    for(r = le;r <= ri;r++)
        if( a[r].pos <= mid ) tmp[p++] = a[r];
        else tmp[q++] = a[r];
    for(r = le;r <= ri;r++)
        a[r] = tmp[r];
    cdq(le, mid);
    int s = 1, t = 0;
    for(p = le;p <= mid;p++) {
        while( s < t && (que[t].y - que[t-1].y)*(a[p].x - que[t].x) >= (a[p].y - que[t].y)*(que[t].x - que[t-1].x) )
            t--;
        que[++t] = a[p];
    }
    for(q = mid + 1;q <= ri;q++) {
        while( s < t && a[q].k*(que[s+1].x - que[s].x) >= (que[s+1].y - que[s].y) )
            s++;
        a[q].dp = min(a[q].dp, a[q].c + que[s].y - que[s].x*a[q].k);
    }
    cdq(mid + 1, ri);
    p = le, q = mid + 1, r = le;
    while( p <= mid && q <= ri ) {
        if( a[p].x == a[q].x )
            tmp[r++] = (a[p].y < a[q].y) ? a[p++] : a[q++];
        else tmp[r++] = (a[p].x < a[q].x) ? a[p++] : a[q++];
    }
    while( p <= mid )
        tmp[r++] = a[p++];
    while( q <= ri )
        tmp[r++] = a[q++];
    for(r = le;r <= ri;r++)
        a[r] = tmp[r];
}
int main() {
    int n; scanf("%d", &n);
    for(int i=1;i<=n;i++)
        scanf("%lld", &a[i].h), a[i].pos = i;
    for(int i=1;i<=n;i++)
        scanf("%lld", &a[i].w), a[i].w += a[i-1].w;
    for(int i=1;i<=n;i++)
        a[i].k = 2*a[i].h, a[i].c = a[i].h*a[i].h + a[i-1].w, a[i].dp = INF;
    a[1].dp = 0;
    sort(a+1, a+n+1, cmp); cdq(1, n);
    for(int i=1;i<=n;i++)
        if( a[i].pos == n ) printf("%lld\n", a[i].dp);
}

@[email protected]

cdq 分治真的太巧妙了。

我们需要维护三部分的有序性:斜率,横坐标,编号。

你看 cdq 分治,只需要一点点离线化,就可以顺利解决这三部分的矛盾。

巧妙,太巧妙了。

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/10225397.html

时间: 2024-10-30 17:15:32

@loj - [email protected]「CEOI2017」Building Bridges的相关文章

@loj - [email&#160;protected]「SDOI2014」Lis

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定序列 A,序列中的每一项 Ai 有删除代价 Bi 和附加属性 Ci 请删除若干项,使得 A 的最长上升子序列长度减少至少 1,且付出的代价之和最小,并输出方案. 如果有多种方案,请输出将删去项的附加属性排序之后,字典序最小的一种. 输入格式 输入包含多组数据. 输入的第一行包含整数

@loj - [email&#160;protected] 「CQOI2017」老 C 的方块

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 老 C 是个程序员. 作为一个懒惰的程序员,老 C 经常在电脑上玩方块游戏消磨时间.游戏被限定在一个由小方格排成的 R 行 C 列网格上,如果两个小方格有公共的边,就称它们是相邻的,而且有些相邻的小方格之间的公共边比较特殊. 特殊的公共边排列得有很强的规律.首先规定,第 1 行的前两个

@loj - [email&#160;protected] 「SDOI2017」硬币游戏

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 周末同学们非常无聊,有人提议,咱们扔硬币玩吧,谁扔的硬币正面次数多谁胜利. 大家纷纷觉得这个游戏非常符合同学们的特色,但只是扔硬币实在是太单调了. 同学们觉得要加强趣味性,所以要找一个同学扔很多很多次硬币,其他同学记录下正反面情况. 用 H 表示正面朝上, 用 T 表示反面朝上,扔很多

@loj - [email&#160;protected] 「CTSC2016」时空旅行

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 2045 年,人类的技术突飞猛进,已经找到了进行时空旅行的方法.小 R 得到了一台时空旅行仪,他想用它调查不同时空中人类的发展状况. 根据平行时空理论,宇宙中存在着很多独立的时空,每个时空在下一个时间点还会分化出若干个不同的时空.宇宙是一个三维空间,人类使用空间直角坐标系来描述空间中的

@loj - [email&#160;protected] 「清华集训 2017」生成树计数

目录 @[email protected] @[email protected] @正文@ @补充@ @accepted [email protected] @[email protected] @[email protected] 在一个 s 个点的图中,存在 s - n 条边,使图中形成了 n 个连通块,第 i 个连通块中有 \(a_i\) 个点. 现在我们需要再连接 n - 1 条边,使该图变成一棵树.对一种连边方案,设原图中第 i 个连通块连出了 \(d_i\) 条边,那么这棵树 T 的

loj#2552. 「CTSC2018」假面

题目链接 loj#2552. 「CTSC2018」假面 题解 本题严谨的证明了我菜的本质 对于砍人的操作好做找龙哥就好了,blood很少,每次暴力维护一下 对于操作1 设\(a_i\)为第i个人存活的概率,\(d_i\)为死掉的概率,\(g_{i,j}\)是除i以外活了j个人的概率 那个选中i人的答案就是 \[a_i\times\sum_{j = 0} ^{k - 1}\frac{g_{i,j}}{j + 1}\] 对于\(g_{i,j}\) ,设\(f_{i,j}\)表示前\(i\)个人有\(

loj#2076. 「JSOI2016」炸弹攻击 模拟退火

目录 题目链接 题解 代码 题目链接 loj#2076. 「JSOI2016」炸弹攻击 题解 模拟退火 退火时,由于答案比较小,但是温度比较高 所以在算exp时最好把相差的点数乘以一个常数让选取更差的的概率降低 代码 #include<ctime> #include<cmath> #include<cstdio> #include<cstring> #include<algorithm> #define gc getchar() #define

Loj #2541「PKUWC2018」猎人杀

Loj #2541. 「PKUWC2018」猎人杀 题目链接 好巧妙的题! 游戏过程中,概率的分母一直在变化,所以就非常的不可做. 所以我们将问题转化一下:我们可以重复选择相同的猎人,只不过在一个猎人被选择了过后我们就给他打上标记,再次选择他的时候就无效.这样与原问题是等价的. 证明: 设\(sum=\sum_iw_i,kill=\sum_{i被杀死了}w_i\). 攻击到未被杀死的猎人\(i\)的概率为\(P\). 则根据题意\(P=\frac{w_i}{sum-kill}\). 问题转化后:

Loj #2542. 「PKUWC2018」随机游走

Loj #2542. 「PKUWC2018」随机游走 题目描述 给定一棵 \(n\) 个结点的树,你从点 \(x\) 出发,每次等概率随机选择一条与所在点相邻的边走过去. 有 \(Q\) 次询问,每次询问给定一个集合 \(S\),求如果从 \(x\) 出发一直随机游走,直到点集 \(S\) 中所有点都至少经过一次的话,期望游走几步. 特别地,点 \(x\)(即起点)视为一开始就被经过了一次. 答案对 $998244353 $ 取模. 输入格式 第一行三个正整数 \(n,Q,x\). 接下来 \(