ZCMU 1985:小C的数学问题(线段树+分治)或者 栈的实现

Problem Description
小C是个云南中医学院的大一新生,在某个星期二,他的高数老师扔给了他一个问题。 
让他在1天的时间内给出答案。 
但是小C不会这问题,现在他来请教你。 
请你帮他解决这个问题。 
有n个数,每个数有权值。 
数学老师定义了区间价值为区间和乘上区间内的最小值。 
现在要你找出有最大区间价值的区间是什么,并输出区间价值。

Input
每个输入文件只包含单组数据。 
第一行一个整数n。(1 <= n <= 100000) 
第二行n个整数a1,a2,...,an。(0<=ai<=1000000)第二行n个整数a1,a2,...,an。(0<=ai<=1000000)
Output
第一行输出一个整数,表示最大的区间价值。 
第二行输出两个整数,表示区间的起点和终点。 
保证答案唯一。

sample input

6
10 1 9 4 5 9

sample output

108
3 6

这题一看用线段树是解决不了的,因为如果是满足题意的线段树去获得答案的话,复杂度太高。因此这题需要找到突破点在哪?

突破点就是--区间最小值的位置。

对于每个区间,自身的值为区间和*区间最小值,根据区间最小值的位置pos,可以把区间分成左右两边 
总结一下,对于每个区间有 1.当前区间的值,2.pos左边区间的最大值,3.pos右边区间的最大值,三个值取最大值就是当前区间能得到的最大值。 

就是看去掉这个最小值之后,看最小值两边的值是否是有大于当前这个区间的值,如果是大于的话就更新

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long ll;
const int N = 1e5+10;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define Abs(x) ((x)>=0?(x):-(x))
#define lson id<<1
#define rson id<<1|1
struct node{
    int l,r,id;
    ll minv;
}num[N*4];
ll ans=-1;
ll sum[N];
ll a[N];
int n,ansl=0,ansr=0;
void push_up(int id){
    if(num[lson].minv>num[rson].minv)
        num[id].minv=num[rson].minv,num[id].id=num[rson].id;
    else 
    num[id].minv=num[lson].minv,num[id].id=num[lson].id;
}
void build(int l,int r,int id){
    num[id].l=l;
    num[id].r=r;
    if(l==r){
        num[id].minv=a[l];
        num[id].id=l;
        return ;
    }
    int mid=(l+r)/2;
    build(l,mid,lson);
    build(mid+1,r,rson);
    push_up(id);
}
node query(int l,int r,int id) {//查询当前区间的最小值和其下标在哪
    if(l==num[id].l&&r==num[id].r)
        return num[id];
    int mid=(num[id].r+num[id].l)>>1;
    if(r<=mid) return query(l,r,lson);
    else if(l>mid) return query(l,r,rson);
    else{
        node t1=query(l,mid,lson);
        node t2=query(mid+1,r,rson);
        if(t1.minvr)
        return 0;
    if(l==r) {//这个地方需要注意一下,当是一个点的时候也需要判断是否是大于当前的答案
            if(a[l]*a[l]>ans)
            ansl=ansr=l;
            return a[l]*a[l];
    }
    node u=query(l,r,1);
    int p=u.id;//区间[l,r]的最小值的下标
    ll res=(sum[r]-sum[l-1])*u.minv;
    if(res>ans){
        ans=res;
        ansl=l;
        ansr=r;
    }
    return max(res,max(Find(l,p-1),Find(p+1,r)));//这一步就是看去除这个最小值,还有其他的最小值是满足题意的吗(这是陈述句!!!)
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    #endif // ONLINE_JUDGE
    scanf("%d",&n);
    for(int i=1;i<=n;i++)//在这里我们求一个前缀和来获得区间值
        scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i];
    build(1,n,1);//线段树建树,快速实现查询区间的最小值和下标
    printf("%lld\n",Find(1,n));
    printf("%d %d\n",ansl,ansr);
    return 0;
 }

或者这道题目还可以用栈去模拟实现,每一次去入栈的目的就是去找当前栈顶这个元素的能到达的区间范围是多少

什么意思涅~~

拿样例来说:

10 1 9 4 5 9

如果以10为最小值的话,10能覆盖的区间就是[1,1]

如果以1为最小值的话,1能覆盖的区间就是[1,6]

如果以9为最小值的话,9能覆盖的区间就是[3,3]

如果以4为最小值的话,4能覆盖的区间就是[3,6]

………………

依次那么去写出来

然后用栈的目的就是去模拟这个过程

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long ll;
const int N = 1e6+10;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define Abs(x) ((x)>=0?(x):-(x))
ll sum[N],a[N];
struct node{
    int l,r,id;
    ll val;
}num[N];
stackst;
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    #endif // ONLINE_JUDGE
    int n;
    scanf("%d",&n);
    rep(i,1,n) scanf("%lld",&num[i].val),sum[i]=sum[i-1]+num[i].val;
    rep(i,1,n) num[i].r=num[i].l=i,num[i].id=i;
    st.push(num[1]);
    for(int i=2;i<=n;i++){
        if(!st.empty()){
            if(st.top().valnum[i].val){
                node tt=st.top();
                num[tt.id].r=i-1;
                st.pop();
                }
                if(!st.empty()){
                num[i].l=st.top().id+1;//这个地方要注意一下
              st.push(num[i]);
              }
            }
          }
          if(st.empty()){
            num[i].l=1;
            st.push(num[i]);
            }
    }
    //如果栈里面还有其他的元素,那么所能到达的右边界都是n
    while(!st.empty()){
        node tt=st.top();
        num[tt.id].r=n;
        st.pop();
    }
    //枚举这n个数去找到最大的值
    node ans;
    ans.val=-1;
    for(int i=1;i<=n;i++){
//        cout<ans.val){
            ans.val=t;
            ans.l=num[i].l;
            ans.r=num[i].r;
        }
    }
    printf("%lld\n%d %d\n",ans.val,ans.l,ans.r);
    return 0;
}

 

你可能感兴趣的:(思维,线段树)