2020-2021 ACM-ICPC, Asia Seoul Regional Contest 补题
题目传送门
朴素算法(暴搜):o(
n
2
n^2
n2)
决策单调:o(n*
log
2
n
\log_2^n
log2n)
题意:
给一长度n(n<=1e6)的数组, h i h_i hi<=1e6,求max( h i h_i hi+ h j h_j hj)∗(j−i) 1≤i<j≤n.
思路:
直接求很难,进行公式变换 (
h
j
h_j
hj - ( -
h
i
h_i
hi ) )∗(j−i) 1≤i<j≤n.
之后就转换成求在直角坐标系里求 矩形面积的问题。 ( j,
h
j
h_j
hj ) ( i,
h
i
h_i
hi )
这个时候决策单调性优化DP就派上用场了。
决策单调性:
啥?啥?啥? 啥是决策单调性?
就是说,对于任意a<b<c<d,若满足在c处转移到b比a优,那么在d处也满足。
分析:
对于此题怎么做呢?
首先需要有一个结论(其实也显而易见),假如对于一个点i来说,它的最佳点在j(i < j),那么对于任意一个点p(p < i),p的最佳点一定<=j。
先创建两个数组: a,b数组, 以下标为x坐标,值为y坐标。并且规定a数组点在第一象限,b数组在第四象限(但b数组的值还是正数,后会有图)。
得到a,b数组
(
h
j
h_j
hj - (
h
i
h_i
hi ) )∗(j−i) 1≤i<j≤n.
对于一个固定的(j,a[j]),选择的(i,b[i])显然位置右下越优.
那么对于(i,b[i])和(j,b[j]),如果i<j且b[i]>b[j],
那么(i,b[i])完全可以删去,所以可以的b数组。
eg: 一串数 1 3 2 5 4
遍历:
我这里是以1点固定,看他右边的点(也就是3点)。(对角线确定一个矩形)
对于b数组的情况:
固定1号点,找3号点。
先看见1 后面没有比他更小的 所以b: 1
下一个是3,后面的数字有比3小的,所以舍去 此时b:1
下一个是2,后面的数字没有2还小的,所以留下 此时b:1 2
下一个是5,后面的数字有比5小的数字;所以舍去: 此时b: 1 2
下一个是4, 后面的数字,无了, 所以留下, 此时: 1 , 2, 4;
所以得到的b数组值:1, 2,4。
发现没, b数组是单调的,递增.
具体为什么呢:每次是否留下这个数字:都要看后面有没有更小的,有就舍去了,没有就留下,所以说后面的数字都比该数字要大。
对于a组数的情况同理:
得到是:1 3 5. (也是单调的).
可能会有些迷惑为什么?
是这样的,前面分析b数组把1号点固定,所以这里分析得到a数组就要把3号点固定。
(ps:那我前面分析a数组是固定4号点,也是可以的,那么这时分析得到b数组就要为固定2号点,当然得到的a,b数组值就会不同)
这里我是以1号,3号点形成对角线来分析的。(这里是为统一规则).
来吧~
固定3号点,找1号点;
先看见4,前面的数字有比4大的点,所以舍去 ,当前a数组:无。(为什么舍去呢,以为前面的数字5,形成的面积显然比他大)。
然后看见5,前面的数字没有比大的,所以留下, 当前a数组:5.
然后看见2,前面的数字有比2大的点,所以舍去 ,当前a数组:5.
然后看见3,前面的数字没有比大的,所以留下, 当前a数组:3,5.
之后看见1,前面没有数字可比较,所以留下:当前a数组:1,3,5;
最终得到a数组为1,3,5.
构造递归,关键。
1.在a数组中,找出中间的点,mid (如下图)
2.找出以mid点(1号点) 围成最大的面积, 令找出来的点为pos.
3.关键来了!!! 那么对mid左边的点 能找出最大面积的点 一定在pos左边或是pos点。
mid右边的点,能找出最大面积的点一定在pos的右边或是pos点
这里 为方便表示 s(i,j) 对角表示矩形面积。
对mid左边的点 能找出最大面积的点 一定在pos左边或是pos点。
为什么?
这里我们使用反证法,就是说明pos以右的点都比pos点小即可。
目标就是证明:s(i,pos) >s(i,j)成立即可。
前面的前提说了 s(mid,pos)> s(mid,j)
所以 s(mid,pos)与s(mid,j)面积 不重合的部分:有①>②+③;
对于:s(i,pos) 与s(i,j)面积不重合的部分:有:①+④>②;
所以显然:s(i,pos) >s(i,j) 成立。 得证完毕。
我觉得吧!人生总要留点遗憾才算完美。
这里的证明不再复述,同理:mid右边的点,能找出最大面积的点一定在pos的右边(举一反三,思路是一样的).
所以我们要求最大面积就是三者之间,mid左边 mid mid右边。 mid左右边接着套娃呗~
代码如下(配有注释):
#include<iostream>
#include<algorithm>
#include<math.h>
#include<cstring>
#include<vector>
#include<queue>
#define ll long long
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=1e6+10;
const ll mod=1e9+7;
ll read(){
ll s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
int n;
ll a[N], b[N],h[N];
int cnta, cntb;
ll calc(int i,int j){ // 传入下标 求面积
return (h[a[i]]+h[b[j]])*(b[j]-a[i]);
}
using namespace std;
ll solve(int l1,int r1,int l2,int r2){
if(l1>r1||l2>r2)
return 0;
int mid = (l1 + r1) >> 1;
int pos = l2;
ll s = calc(mid,l2);
for (int i = l2+1; i <= r2;i++){
if(a[mid]<b[i]){
ll ans = calc(mid, i);
if(s<ans){
//找出最优的pos点.
s = ans;
pos = i;
}
}
}
//比较mid左边 mid mid右边 的最大值
s = max(s,solve(l1, mid-1, l2, pos));
s = max(s,solve(mid+1, r1, pos, r2));
return s;
}
int main (){
n = read();
for (int i = 1; i <= n;i++)
h[i] = read();
//这里的a,b数组存放的都是h数组的下标,与上文的存值不同,但是一样
//存下标是为了方便 二分嘛。
for (int i = 1; i <= n;i++){
if(cnta==0)
a[++cnta] = i;
else if(h[i]>h[a[cnta]])
a[++cnta] = i;
}
for (int i = n; i >=1;i--){
if(cntb==0)
b[++cntb] = i;
else if(h[i]>h[b[cntb]])
b[++cntb] = i;
}
//b数组逆转一下就好了。
reverse(b + 1, b + 1 + cntb);
/* for(int i=1;i<=cntb;i++){
cout<<h[b[i]]<<" ";
} */
cout<<solve(1,cnta,1,cntb);
return 0;
}
感谢观看!!
都看到最后了,点个赞呗~(doge)
参考资料:
风浔凌:2020-2021 ACM-ICPC, Asia Seoul Regional Contest 补题|题解
live4m:gym102920 L. Two Buildings(决策单调性+分治)