题目大意:给n个数,求一个区间,使这个区间数字之和*这个区间最小值最大,给出这个最大值以及这个区间左右端点。
题目分析:笛卡尔树。先按输入建一颗小堆笛卡尔树,然后dfs遍历一遍,直接求解。O(n)完美解决!
笛卡尔树首先是一颗二分查找树,每一颗子树的dfs序列都是原序列的连续的子序列。再利用笛卡尔树堆的性质可以O(1)找出这个连续的子序列最小值,直接更新最大值即可。
因为要求这个区间的左右端点,那么每个节点要记录一下当前子树所表达区间的左右端点,这个很容易,dfs的时候就可以顺便求出来了。
详情请见代码:
#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 100005;
typedef __int64 ll;
struct node
{
ll val,sum;
int l,r,pa,id,lla,rra;
}lcm[N];
int stk[N];
int n,ls,rs;
ll ans;
int build()
{
int i,top,j;
top = -1;
for(i = 1;i <= n;i ++)
{
j = top;
while(j >= 0 && lcm[stk[j]].val > lcm[i].val) j --;
if(j != -1)
{
lcm[i].pa = stk[j];
lcm[stk[j]].r = i;
}
if(j < top)
{
lcm[stk[j + 1]].pa = i;
lcm[i].l = stk[j + 1];
}
stk[++j] = i;
top = j;
}
lcm[stk[0]].pa = -1;
return stk[0];
}
ll dfs(int rt)
{
if(rt == -1)
return 0;
lcm[rt].sum = dfs(lcm[rt].l) + dfs(lcm[rt].r) + lcm[rt].val;
if(lcm[rt].l != -1)
lcm[rt].lla = lcm[lcm[rt].l].lla;
if(lcm[rt].r != -1)
lcm[rt].rra = lcm[lcm[rt].r].rra;
if(ans <= lcm[rt].sum * lcm[rt].val)
{
ans = lcm[rt].sum * lcm[rt].val;
ls = lcm[rt].lla;
rs = lcm[rt].rra;
}
return lcm[rt].sum;
}
int main()
{
int i;
while(scanf("%d",&n) != EOF)
{
for(i = 1;i <= n;i ++)
{
scanf("%I64d",&lcm[i].val);
lcm[i].id = i;
lcm[i].sum = 0;
lcm[i].lla = lcm[i].rra = i;
lcm[i].l = lcm[i].r = lcm[i].pa = -1;
}
int root = build();
ans = -1;
ls = rs = -1;
dfs(root);
printf("%I64d\n%d %d\n",ans,ls,rs);
}
return 0;
}