首先ORZ一发Claris聚聚的题解:http://www.cnblogs.com/clrs97/p/8689215.html,不然我可能没机会补过这道神题了。

这里写一个更详细的题解吧(我还是太菜了啊)。

题目描述

有\(n(n \le10^5)\)个人依次进入一个入口,要到一个出口。入口到出口有两条同样长的路。每个人都有一个速度,用通行时间\(a_i(1\le a_i \le 10^6)\)表示,他可以选择任一条路走。但是,若走这条路的前面的人比他慢的话,他只能降到和前面所有人最慢的那个人同样的速度(从而会多花时间)。现在请规划每个人选哪条路,使得每个人因等前面的人而浪费的时间尽可能少。

Sample Input

5
100 4 3 2 1

Sample Output


详细题解

此题很容易用DP来做。考虑前\(i\)个人,则两个楼梯必有一个的通行时间变为前\(i\)个人最慢的那个,我们设\(dp[i][j]\)表示前\(i\)个人另一个楼梯当前通行时间是\(j\)(\(j\)从小到大离散化)时的最优答案,则考虑\(dp[i+1]\)和\(dp[i]\)的关系:

(1)若\(a[i+1] \ge max(a[1..i])\),则显然\(dp[i+1][j]=dp[i][j]\);

(2)若\(a[i+1]<max(a[1..i])\),则:

情况1:\(j\)对应状态快的那个楼梯比\(a[i+1]\)时间短,且选这个楼梯,于是\(dp[i+1][k]=min(dp[i][j],j\le k)\),其中\(k\)为\(a[i+1]\)离散化的结果;

情况2:\(j\)对应状态快的那个楼梯比\(a[i+1]\)时间短,但选最慢的楼梯,于是\(dp[i+1][j]=dp[i][j]+max(a[1..i])-a[i+1]\),其中\(j<k\);

情况3:\(j\)对应状态快的那个楼梯比\(a[i+1]\)的时间长,那必然选这个楼梯,于是\(dp[i+1][j]=dp[i][j]+f[j]-a[i+1]\),其中\(j>k\),\(f[j]\)表示第\(j\)小的值。

这样状态数和转移复杂度均为\(n^2\)。下面考虑数据结构优化。

我们需要维护的dp要支持区间最小值查询,单点修改,区间增加,和区间\(dp[i][j]+=f[j]\)。

如果没有最后的操作此题直接用线段树就简单多了。

加上了这种操作,考虑分块。每块首先要维护增量tag,该tag对最值无影响。下面主要考虑\(dp[i][j]+=f[j]\)。

注意到一个性质:若\((dp[i][j+1]-dp[i][j])/(f[j+1]-f[j])<(dp[i][j]-dp[i][j-1])/(f[j]-f[j-1])\),那么无论再怎么增加\(dp[i][j]\)也不可能最优。所以将\(j\)下标看做二维点\((f[j],dp[i][j])\)后,所有可能的最优值形成一个下凸壳。当整块\(dp[i][j]+=f[j]\)后,凸壳上仍是这些点,但最小值点可能将向左移动。于是我们只要不断删除凸壳右边的点,就可以每一块均摊\(O(1)\)的修改和查询最小值。

对于单点修改,只需要重构凸壳,复杂度为块大小。

现在考虑分块后从\(dp[i]\)转移到\(dp[i+1]\)的总复杂度,设块大小\(b\)。由于单点修改仅一个点\(k\),故复杂度\(b\);取最小值复杂度\(b+n/b\);区间加复杂度\(b+n/b\);区间\(dp[i][j]+=f[j]\)复杂度\(b+n/b\)。当\(b\)取\(\sqrt n\) 时复杂度最优,为\(\sqrt n \)。考虑到重构凸壳较慢,应在求最值时如需要再重构凸壳。

总时间复杂度\(O(n \sqrt n)\)

AC代码

 #include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
#define LL long long
struct Block{
LL a[], tag, delta;
int order[];
int pos[], back, n;
bool flag;
void init(int b[], int size){
n = size; flag = true;
memcpy(order, b, sizeof(int)*n);
memset(a, 0x3f, sizeof(LL)*n);
}
bool check(int j1, int j, int j2){
return (a[j2] - a[j]) * (order[j] - order[j1]) <= (a[j] - a[j1]) * (order[j2] - order[j]);
}
LL get(int i){ return a[i] + tag * order[i] + delta; }
void update(){
for (int i = ; i < n; i++)
a[i] = get(i);
tag = delta = ; back = ;
flag = false;
for (int i = ; i < n; i++){
while (back> && check(pos[back - ], pos[back], i))back--;
pos[++back] = i;
}
while (back > && get(pos[back - ]) <= get(pos[back]))back--;
}
void set(int i, LL val){
a[i] += val - get(i);
flag = true;
}
void add(int l, int r, int d){
if (l == && r == n - )delta += d;
else{
for (int i = l; i <= r; i++)
a[i] += d;
flag = true;
}
}
void add2(int l, int r){
if (l == && r == n - ){
tag++;
while (back > && get(pos[back - ]) <= get(pos[back]))back--;
}
else{
for (int i = l; i <= r; i++)
a[i] += order[i];
flag = true;
}
}
LL queryMin(int l, int r){
if (l == && r == n - ){
if (flag)update();
return get(pos[back]);
}
LL ret = 1LL << ;
for (int i = l; i <= r; i++)
ret = min(ret, get(i));
return ret;
}
}b[];
int a[], order[];
int belong[], offset[], blockSize;
void add(int l, int r, int delta){
int start = l / blockSize, end = r / blockSize;
for (int i = start; i <= end; i++)
b[i].add(i == start ? offset[l] : , i == end ? offset[r] : b[i].n - , delta);
}
void add2(int l, int r){
int start = l / blockSize, end = r / blockSize;
for (int i = start; i <= end; i++)
b[i].add2(i == start ? offset[l] : , i == end ? offset[r] : b[i].n - );
}
LL queryMin(int l, int r){
int start = l / blockSize, end = r / blockSize;
LL ret = 1LL << ;
for (int i = start; i <= end; i++)
ret = min(ret, b[i].queryMin(i == start ? offset[l] : , i == end ? offset[r] : b[i].n - ));
return ret;
}
int main(){
int n;
scanf("%d", &n);
for (int i = ; i <= n; i++){
scanf("%d", &a[i]);
order[i] = a[i];
}
order[] = ;
sort(order, order + n + );
int cnt = unique(order, order + n + ) - order;
blockSize = sqrt(cnt);
int j = , k = ;
for (int i = ; i < cnt; i++){
belong[i] = k;
offset[i] = j++;
if (j == blockSize){
b[k].init(order + i - j + , j);
j = ; k++;
}
}
if (j)b[k].init(order + cnt - j, j);
b[].set(, );
int mpos = ;
for (int i = ; i <= n; i++){
int pos = lower_bound(order, order + cnt, a[i]) - order;
if (pos >= mpos)mpos = pos;
else{
LL val = queryMin(, pos);
b[belong[pos]].set(offset[pos], val);
add(, pos - , order[mpos] - order[pos]);
add(pos + , mpos, -order[pos]);
add2(pos + , mpos);
}
}
printf("%lld", queryMin(, cnt - ));
}

XVIII Open Cup named after E.V. Pankratiev. Grand Prix of Khamovniki Problem J Stairways解题报告(分块+维护凸壳)的更多相关文章

  1. XVIII Open Cup named after E.V. Pankratiev. Grand Prix of Khamovniki

    A. Ability Draft 记忆化搜索. #include<stdio.h> #include<iostream> #include<string.h> #i ...

  2. XVIII Open Cup named after E.V. Pankratiev. Grand Prix of SPb

    A. Base $i - 1$ Notation 两个性质: $2=1100$ $122=0$ 利用这两条性质实现高精度加法即可. 时间复杂度$O(n)$. #include<stdio.h&g ...

  3. XVIII Open Cup named after E.V. Pankratiev. Grand Prix of Siberia

    1. GUI 按题意判断即可. #include<stdio.h> #include<iostream> #include<string.h> #include&l ...

  4. XVIII Open Cup named after E.V. Pankratiev. Grand Prix of Peterhof

    A. City Wall 找规律. #include<stdio.h> #include<iostream> #include<string.h> #include ...

  5. XVIII Open Cup named after E.V. Pankratiev. Grand Prix of Korea

    A. Donut 扫描线+线段树. #include<cstdio> #include<algorithm> using namespace std; typedef long ...

  6. XVIII Open Cup named after E.V. Pankratiev. Grand Prix of Saratov

    A. Three Arrays 枚举每个$a_i$,双指针出$b$和$c$的范围,对于$b$中每个预先双指针出$c$的范围,那么对于每个$b$,在对应$c$的区间加$1$,在$a$处区间求和即可. 树 ...

  7. XVII Open Cup named after E.V. Pankratiev. Grand Prix of America (NAIPC-2017)

    A. Pieces of Parentheses 将括号串排序,先处理会使左括号数增加的串,这里面先处理减少的值少的串:再处理会使左括号数减少的串,这里面先处理差值较大的串.确定顺序之后就可以DP了. ...

  8. XVII Open Cup named after E.V. Pankratiev Grand Prix of Moscow Workshops, Sunday, April 23, 2017 Problem D. Great Again

    题目: Problem D. Great AgainInput file: standard inputOutput file: standard outputTime limit: 2 second ...

  9. XVII Open Cup named after E.V. Pankratiev Grand Prix of Moscow Workshops, Sunday, April 23, 2017 Problem K. Piecemaking

    题目:Problem K. PiecemakingInput file: standard inputOutput file: standard outputTime limit: 1 secondM ...

随机推荐

  1. 【BZOJ4300】绝世好题(位运算水题)

    点此看题面 大致题意: 给你一个序列\(a\),让你求出最长的一个子序列\(b\)满足\(b_i\&b_{i-1}!=0\). 位运算+\(DP\) 考虑设\(f_i\)表示以第\(i\)个数 ...

  2. C语言 数组名不是首地址指针

    今天上计算机系统课的时候老师讲到了C中的聚合类型的数据结构.在解释数组名的时候说"数组名是一个指针,指向该数组的第一个元素",附上ppt(第二行): 我觉得这是不正确的,是一个常见 ...

  3. solr数据分片相关

    solr操作url 使用正常的core,使用命令生成coillection solr create_collection -c students2 -d ../server/solr/my/conf ...

  4. 前端安全之XSS和csrf攻击

    1.Csrf攻击概念: csrf攻击(Cross-site request forgery):跨站请求伪造; 2.Csrf攻击原理: 用户是网站A的注册用户,且登录进去,于是网站A就给用户下发cook ...

  5. EL和JSTL表达式学习记录

    EL.JSTL表达式EL:1.EL使用目的:代替jsp页面中java脚本编写 2.EL作用:(1)从域中取出数据(最重要)(2)EL的内置对象(11个)(3)EL可以执行表达式运算 JSTL(JSP标 ...

  6. python之列表推导、迭代器、生成器

    http://blog.chinaunix.net/uid-26722078-id-3484197.html 1.列表推导 看几个例子,一切就明白了. #!/usr/bin/python number ...

  7. AT2386 Colorful Hats (乱搞题,思维题)

    分情况讨论的神题... max不等于min + 1 或者不等于min,这种情况显然不存在. 如果都等于一个数 有两种情况: 互相独立,那么a[i]肯定==n-1 有相同的,那么a[i]一定不是独立的. ...

  8. tp5依赖注入(自动实例化):解决了像类中的方法传对象的问题

    app\index\Demo1.php namespace app\index\controller; /* 容器与依赖注入的原理 ----------------------------- 1.任何 ...

  9. Flask初学者:配置文件

    如果设置项比较少的话可以使用“app.config['param_name']=value”的形式直接使用,如果需要设置的参数比较多的话,可以单独新建一个配置文件用来存放配置信息,配置文件中的参数需大 ...

  10. 51nod 1202 不同子序列个数(计数DP)

    1202 子序列个数 基准时间限制:1 秒 空间限制:131072 KB 分值: 40      子序列的定义:对于一个序列a=a[1],a[2],......a[n].则非空序列a'=a[p1],a ...