[JSOI2008]Blue Mary開公司

Description!

Input
第一行 :一個整數N ,表示方案和詢問的總數。
接下來N行,每行開頭一個單詞“Query”或“Project”。
若單詞爲Query,則後接一個整數T,表示Blue Mary詢問第T天的最大收益。
若單詞爲Project,則後接兩個實數S,P,表示該種設計方案第一天的收益S,以及以後每天比上一天多出的收益P。
1 <= N <= 100000 1 <= T <=50000 0 < P < 100,| S | <= 10^6
提示:本題讀寫數據量可能相當巨大,請選手注意選擇高效的文件讀寫方式。

Output
對於每一個Query,輸出一個整數,表示詢問的答案,並精確到整百元(以百元爲單位,例如:該天最大收益爲210或290時,均應該輸出2)。
沒有方案時回答詢問要輸出0

Sample Input
10
Project 5.10200 0.65000
Project 2.76200 1.43000
Query 4
Query 2
Project 3.80200 1.17000
Query 2
Query 3
Query 1
Project 4.58200 0.91000
Project 5.36200 0.39000

Sample Output
0
0
0
0
0

寫這個題目,我們需要用到線段樹的標記永久化。那麼什麼是線段樹的標記永久化呢?
所謂標記永久化,是指線段樹的標記不會下傳,那麼我每次詢問的時候把葉子到根的路徑上的信息處理下就好了。
不會下傳的標記?那有什麼用?
用處其實非常多,例如常數小,或者說有些題目在標記下傳的時候無法維護信息,這樣可以用標記永久化。

那麼這題呢?的確是標記永久化,不過不絕對。
首先本題的方案都可以看做是一條直線,那麼這題便轉化成了插入n條直線,然後詢問某x位置的最大的y。

每次加入一條直線時,首先判斷該直線與區間所記錄的直線(記錄在Mid)的斜率大小,然後判斷在Mid點時,哪條直線的答案更優

如果是斜率大的答案更優,那麼在(Mid,r]內,一定是斜率大的答案優,因此我們只需要將斜率小的直線下放到左兒子,並且將當前區間記錄的直線更新爲斜率大的直線;如果是斜率小的答案更優,那麼在[l,Mid]內一定是斜率小的更優,所以我們就將斜率大的直線傳到右兒子中遞歸更新,當前區間同樣更新

其實讀到這裏應該還會有個小問題,由於線段樹上記錄的是一段段的折線,並且這些折線斜率必定遞增。我們在上文所說的某條直線在更新了區間記錄的答案後,就把另一條直線往一邊下放了,難道另一邊不用下放嗎?又或者說,當前記錄的新直線,不用在另一邊下放,判斷一下,更新一下嗎?

標記永久化帶來的這個疑問,必然有解決的方法。我們統計答案的時候,將路過的所有直線都計算一下,更新答案,就不會有什麼問題了。

又或者我們換個思想,每個區間所記錄的直線,只是保證Mid最優的直線,那麼我們的問題也就解決了

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
inline int read(){
    int x=0,f=1;char ch=getchar();
    for (;ch<'0'||ch>'9';ch=getchar())  if (ch=='-')    f=-1;
    for (;ch>='0'&&ch<='9';ch=getchar())    x=(x<<1)+(x<<3)+ch-'0';
    return x*f;
}
inline void print(int x){
    if (x>=10)     print(x/10);
    putchar(x%10+'0');
}
const int N=1e5,Day=5e4;
double K[N+10],B[N+10];  //K是斜率,B是與y軸交點(來自初中數學的摧殘)
int Lazy[N*4+10];  //標記記錄的不是斜率,是序號
char s[10];
#define ls (p<<1)
#define rs (p<<1|1)
bool check(int x,int y,int cnt){return K[x]*(cnt-1)+B[x]>K[y]*(cnt-1)+B[y];}//位於cnt點的兩直線判斷
void change(int p,int l,int r,int t){
    if (l==r){
        if (check(t,Lazy[p],l)) Lazy[p]=t;
        return;
    }
    int mid=(l+r)>>1;
    if (K[t]>K[Lazy[p]]){//將判斷對照前面的文字說明便十分明瞭
        if (check(t,Lazy[p],mid))   change(ls,l,mid,Lazy[p]),Lazy[p]=t;
        else    change(rs,mid+1,r,t);
    }
    if (K[t]<K[Lazy[p]]){
        if (check(t,Lazy[p],mid))   change(rs,mid+1,r,Lazy[p]),Lazy[p]=t;
        else    change(ls,l,mid,t);
    }
}
double get(int x,int cnt){return K[x]*(cnt-1)+B[x];}//得到cnt點的答案
double query(int p,int l,int r,int t){
    if (l==r)   return get(Lazy[p],t);
    int mid=(l+r)>>1;
    double ans=get(Lazy[p],t);  //一路更新答案
    if (t<=mid) ans=max(ans,query(ls,l,mid,t));
    if (t>mid)  ans=max(ans,query(rs,mid+1,r,t));
    return ans;
}
int main(){
    int n=read(),cnt=0;
    for (int i=1;i<=n;i++){
        scanf("%s",s+1);
        if (s[1]=='P'){
            cnt++;
            scanf("%lf%lf",&B[cnt],&K[cnt]);
            change(1,1,Day,cnt);  //題目並未告訴你明確的天數,因此只能這麼玩
        }
        if (s[1]=='Q'){
            int x=read();
            double t=query(1,1,Day,x);
            printf("%d\n",(int)t/100);  //按題目要求輸出
        }
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章