Hướng dẫn giải của duong3982oj Contest 02 - Dãy con tăng dài nhất


Chỉ dùng lời giải này khi không có ý tưởng, và đừng copy-paste code từ lời giải này. Hãy tôn trọng người ra đề và người viết lời giải.
Nộp một lời giải chính thức trước khi tự giải là một hành động có thể bị ban.

Tác giả: huyjavalt

Mấu chốt của bài này là nhận ra ta có thể dp như bài dãy con tăng dần bình thường 😁

Ta gọi ~dp[r]~ là độ dài đoạn con tăng dài nhất kết thúc ở ~r~

Bây giờ ta sẽ chứng minh một điều là : Với mỗi con ~r~ ta sẽ chỉ được ghép với các đầu mút ~r'~ khác bé hơn ~r~

Ta sẽ chứng minh bằng phản chứng như sau

Chứng minh :

TH1 : đoạn ~[l', r']~ giao 1 phần với đoạn ~[l,r]~

Dễ thấy ta sẽ ko chọn như thế này vì đoạn ~[l,r]~ chắc chắn sẽ dài hơn

image.png

TH2 : đoạn ~[l', r']~ bao hoàn toàn đoạn ~[l,r]~

image.png

Mặc dù đúng là đoạn ~[l',r]~ là đoạn dài nhất kết thúc ở ~r~ nhưng mà liệu nó có thực sự tối ưu?

Dễ thấy, đoạn ~[l',r']~ vẫn là đoạn dài nhất trong trường hợp này vậy nếu ta thực sự muốn chọn đoạn ~[l',r]~ cho ~dp[r]~ ta sẽ phải tìm ra trường hợp mà nó sẽ đóng góp cho ~1~ đoạn dài hơn ~[l',r']~

Ta sẽ gọi đoạn đó là ~[l*,r*]~, đoạn này có những trường hợp như sau

- Tất cả trường hợp với ~[l*,r*]~ nhưng ~l* < l' < l~

image.png

Cũng giống TH1, ở đây ta mà nối thì sẽ luôn ra 1 đoạn ~<~ ~[l*,r*]~ nên nó sẽ ko đóng góp gì cho ~dp[r*]~

- ~[l*,r*]~ có ~r* < r~

image.png

Dễ thấy trường hợp này nó chả đóng góp được gì cho đáp án cả 😁

- ~[l*,r*]~ có ~r' > r* > r~

image.png

Tương tự trường hợp này nó sẽ bỏ qua đoạn ~[l',r]~ mà dù nó có dùng thì đoạn ~[l',r']~ vẫn là đoạn dài nhất

- ~[l*,r*]~ có ~r* > r'~

image.png

Trường hợp này ta nhận ra 1 điều là chả dại gì mà chọn đoạn ~[l',r]~ để nối thêm mà thay vào đó sẽ nối bằng đoạn ~[l',r']~ từ đó ra được đoạn ~[l',r*]~

Từ đó ta suy ra được điều phải chứng minh 😙, đến đây thì sẽ chỉ còn là 1 bài dp tối ưu bằng segment tree cơ bản thôi.

~O(nlog_{2}n)~

Ta sẽ for từng đoạn ~[l,r]~ theo thứ tự được thêm vào vào dùng segment tree để tìm ra đáp án

Đầu tiên ~dp[r] = max(dp[r], r-l+1)~

TH1 : ~r' < l~

image.png

Với trường hợp này thì ta sẽ chỉ làm ~dp[r] = max(dp[r], dp[r'] + r-l+1)~

TH2 : ~l < r' < r~

image.png

Với trường hợp này thì ta sẽ làm ~dp[r] = max(dp[r], dp[r'] + r-r')~

Và ta sẽ tạo... 2 cái segment tree để quản lý cả 2 trường hợp này 😅 (Mình nghĩ cách này hơi phiền nên mình nghĩ sẽ có bạn nghĩ ra cách khác tốt hơn)

Segment tree 1 đơn giản chỉ lưu ~dp[r]~ cho mỗi con ~r~ Segment tree 2 sẽ lưu ~dp[r] - r~ cho mỗi con ~r~

Lúc này, với mỗi con ~r~ ta sẽ query max trong 1 đoạn :

  • Đoạn 1 sẽ dùng segment tree 1 trong ~[1,l-1]~
  • Đoạn 2 sẽ dùng segment tree 2 trong ~[l,r]~

Xong rồi thì ta sẽ point update cho 2 con segment tree ở vị trí r thôi 😋

LƯU Ý : Nếu ~dp[x] = dp[y]~ hay ~dp[x] - x = dp[y] - y~ thì chọn con ~x~ hoặc ~y~ bé hơn vì nó sẽ cho mình nhiều cơ hội hơn

Code mẫu

#include <bits/stdc++.h>

using namespace std;

#define int long long

pair<int,int> a[100005];
int dp[200005];
vector<int> v;
int t[800005];
int t2[800005];

int maximize(int x, int y){
    if(x == 0) return y;
    if(y == 0) return x;
    if(dp[x] > dp[y]) return x;
    else if(dp[x] < dp[y]) return y;
    else{
        if(v[x-1] < v[y-1]) return x;
        else return y;
    }
}

void update(int v, int tl, int tr, int pos, int val){
    if(tl == tr) t[v] = tl;
    else{
        int tm = (tl+tr)/2;
        if(pos <= tm) update(v*2,tl,tm,pos,val);
        else update(v*2+1,tm+1,tr,pos,val);
        t[v] = maximize(t[v*2],t[v*2+1]);
    }
}

int query(int v, int tl, int tr, int l, int r){
    if(l > r) return 0;
    if(l > tr || r < tl) return 0;
    if(l <= tl && tr <= r) return t[v];
    int tm = (tl+tr)/2;
    return maximize(query(v*2,tl,tm,l,r),query(v*2+1,tm+1,tr,l,r));
}

int maximize2(int x, int y){
    if(x == 0) return y;
    if(y == 0) return x;
    if(dp[x] - v[x-1] > dp[y] - v[y-1]) return x;
    else if(dp[x] - v[x-1] < dp[y] - v[y-1]) return y;
    else{
        if(v[x-1] < v[y-1]) return x;
        else return y;
    }
}

void update2(int v, int tl, int tr, int pos, int val){
    if(tl == tr) t2[v] = tl;
    else{
        int tm = (tl+tr)/2;
        if(pos <= tm) update2(v*2,tl,tm,pos,val);
        else update2(v*2+1,tm+1,tr,pos,val);
        t2[v] = maximize2(t2[v*2],t2[v*2+1]);
    }
}

int query2(int v, int tl, int tr, int l, int r){
    if(l > r) return 0;
    if(l > tr || r < tl) return 0;
    if(l <= tl && tr <= r) return t2[v];
    int tm = (tl+tr)/2;
    return maximize2(query2(v*2,tl,tm,l,r),query2(v*2+1,tm+1,tr,l,r));
}

signed main(){
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> a[i].first >> a[i].second;
        v.push_back(a[i].first);
        v.push_back(a[i].second);
    }
    sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()), v.end());
    for(int i = 1; i <= n; i++){
        int l = a[i].first, r = a[i].second;
        a[i].first = lower_bound(v.begin(),v.end(),l) - v.begin() + 1;
        a[i].second = lower_bound(v.begin(),v.end(),r) - v.begin() + 1;
    }

    dp[0] = -1e17;

    for(int i = 1; i <= n; i++){
        int l = a[i].first, r = a[i].second;
        dp[r] = max(dp[r], v[r-1]-v[l-1]+1);

        int k1 = query(1,1,v.size(),1,l-1);
        int k2 = query2(1,1,v.size(),l,r-1);
        if (k1 > 0){
            dp[r] = max(dp[r], dp[k1] + v[r-1] - v[l-1] + 1);
        }
        if (k2 > 0){
            dp[r] = max(dp[r], dp[k2] + v[r-1] - v[k2-1]);
        }

        update(1,1,v.size(),r,1);
        update2(1,1,v.size(),r,1);
        //cout << dp[r] << ' ' << r << ' ' << v[r-1] << endl;
    }

    int ans = 0;
    for(int i = 1; i <= v.size(); i++) ans = max(ans,dp[i]);
    cout << ans;
    return 0;
}

Bình luận

Hãy đọc nội quy trước khi bình luận.


Không có bình luận tại thời điểm này.