【線段樹】【cogs775】山海經

775.山海經

★★★ 輸入文件:hill.in 輸出文件:hill.out 簡單對比

時間限制:1 s 內存限制:128 MB

【問題描述】

“南山之首日鵲山。其首日招搖之山,臨於西海之上,多桂,多金玉。有草焉,其狀如韭而青華,其名日祝餘,食之不飢……又東三百里,日堂庭之山,多棪木,多白猿,多水玉,多黃金。

又東三百八十里,日猨翼之山,其中多怪獸,水多怪魚,多白玉,多蝮蟲,多怪蛇,名怪木,不可以上。……”

​ 《山海經》是以山爲綱,以海爲線記載古代的河流、植物、動物及礦產等情況,而且每一條記錄路線都不會有重複的山出現。某天,你的地理老師想重遊《山海經》中的路線,爲了簡化問題,老師已經把每座山用一個整數表示他對該山的喜惡程度,他想知道第a座山到第b座山的中間某段路(i,j) 。能使他感到最滿意,即(i,j) 這條路上所有山的喜惡度之和是(c,d)(acdb) 最大值。於是老師便向你請教,你能幫助他嗎?值得注意的是,在《山海經》中,第i 座山只能到達第i+1 座山。

【輸入】

輸入第1行是兩個數,nm2n1000001m100000n 表示一共有n 座山,m 表示老師想查詢的數目。

第2行是n 個整數,代表n 座山的喜惡度,絕對值均小於10000

以下m 行每行有ab 兩個數,1ajbm ,表示第a 座山到第b 座山。

【輸出】

一共有m 行,每行有3個數ijs ,表示從第i 座山到第j 座山總的喜惡度爲s 。顯然,對於每個查詢,有aijb ,如果有多組解,則輸出i 最小的,如果i 也相等,則輸出j 最小的解。

【輸入樣例】

5 3
5 -6 3 -1 4
1 3
1 5
5 5

【輸出樣例】

1 1 5
3 5 6
5 5 4

【題解】

題目大意:給定N 個數,求[L,R] 區間的最大連續子序列的左端點,右端點以及它的和。

首先考慮DP,DP可以快速求[1,R] 區間的最大連續子序列,但對於[L,R] 區間的情況就束手無策了。

想到區間問題有很多用線段樹求,那麼我們就可以用線段樹來做這道題。

f(L,R)[L,R] 區間的最大連續子序列的和,那麼f(L,R)=max{f(L,mid),f(mid+1,R)}

碼一遍代碼,發現連樣例都過不去,仔細想,如果f(L,R)=f(i,j)i<mid,j>mid ,那麼這樣沒有算到。

那我們再定義SumL(L,R),SumR(L,R) ,分別爲從R 往前推的最大連續子序列的和,從L 往後推的最大連續子序列的和,因爲線段樹的性質,可得這段子序列的左端點L ,右端點R ,那麼f(L,R)=max{SumL(L,mid)+SumR(mid+1,R)},mid=L+R2

這樣就把所有情況考慮到了。

對於維護SumL(L,R)
易得SumL(L,R)=max{SumL(L,mid),Sum(L,mid)+SumL(mid+1,R)}mid=L+R2
其中Sum(L,R)LR 的子序列和。

SumR(L,R) 同理。

又因爲需要知道左右端點,再開4個量維護SumL(L,R) 這段子序列的左端點,SumR(L,R) 這段子序列的右端點,以及f(L,R) 的左右端點即可。

那麼這道題就完美解決了。

#include<cstdio>
#include<algorithm>
using namespace std;
struct ad{
    //L,R表示這個節點覆蓋的線段的左端點和右端點,但因爲是線段樹,可以快速得出L和R的值,在此就不定義了 
    int LSum,RL; //LSum表示從L往後,最大的連續喜惡值之和,RL表示LSum這段連續子序列的右端點 
    int RSum,LR; //RSum表示從R往前,最大的連續喜惡值之和,LR表示RSum這段連續子序列的左端點
    int Sum; //Sum爲L到R的數字之和 
    int Max,LMax,RMax; //Max表示L到R中最大的連續喜惡值之和,LMax表示這段連續子序列的左端點,RMax表示這段連續子序列的右端點 
    inline void newnode(int data,int id){LSum=RSum=Sum=Max=data;RL=LR=LMax=RMax=id;}
    //給節點賦初值,因爲只有l==R時需要賦初值,所以LSum,RSum,Sum,Max都等於A[L],RL,LR,LMax,RMax都等於L 
}T[400005];
int N,M,A[100005];
inline int read(){
    int Res=0,f=1;char ch=getchar();
    while (ch>'9'||ch<'0') f=(ch=='-')?-f:f,ch=getchar();
    while (ch>='0'&&ch<='9') Res=Res*10+ch-'0',ch=getchar();
    return Res*f;
}
inline void updata(ad &P,ad A,ad B){ //更新 
    P.LSum=A.LSum;P.RL=A.RL;
    P.RSum=B.RSum;P.LR=B.LR;
    P.Sum=A.Sum+B.Sum;
    P.Max=A.Max;P.LMax=A.LMax;P.RMax=A.RMax;
    if (A.Sum+B.LSum>P.LSum) P.LSum=A.Sum+B.LSum,P.RL=B.RL;
    if (B.Sum+A.RSum>P.RSum) P.RSum=B.Sum+A.RSum,P.LR=A.LR;
    if (A.RSum+B.LSum>P.Max){//題目要求有多組解輸出i最小的,所以先判這一段
        P.Max=A.RSum+B.LSum;
        P.LMax=A.LR;P.RMax=B.RL;
    }
    if (B.Max>P.Max){
        P.Max=B.Max;
        P.LMax=B.LMax;P.RMax=B.RMax;
    }
}
inline void build(int p,int L,int R){
    if (L==R){T[p].newnode(A[L],L);return ;}
    int mid=(R+L)>>1;
    if (L<=mid) build(p<<1,L,mid);
    if (R>mid) build(p<<1|1,mid+1,R);
    updata(T[p],T[p<<1],T[p<<1|1]);
}
inline ad query(int p,int L,int R,int qL,int qR){ //qL,qR表示詢問的L,R 
    if (qL==L&&qR==R) return T[p];
    if (L==R) return T[p];
    int mid=(L+R)>>1;
    if (mid>=qR) return query(p<<1,L,mid,qL,qR); //如果 [L,mid] 區間包含 [qL,qR] 就直接跑下去 
    if (qL>mid) return query(p<<1|1,mid+1,R,qL,qR); //如果 [mid+1,R] 區間包含 [qL,qR] 就直接跑下去 
    ad Ans1=query(p<<1,L,mid,qL,mid),Ans2=query(p<<1|1,mid+1,R,mid+1,qR),Ans; //有交錯區間就和普通線段樹一樣,刷最大的 
    updata(Ans,Ans1,Ans2);
    return Ans;
}
int main()
{
    freopen("hill.in","r",stdin);
    freopen("hill.out","w",stdout);
    N=read();M=read();
    for (int i=1;i<=N;i++) A[i]=read();
    build(1,1,N);
    for (int i=1;i<=M;i++) {
        int L=read(),R=read();
        ad Res=query(1,1,N,L,R);
        printf("%d %d %d\n",Res.LMax,Res.RMax,Res.Max);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章