• [USACO12MAR]花盆Flowerpot 二分答案+单调队列


    题意:

    给出N滴水的坐标,y表示水滴的高度,x表示它下落到x轴的位置。

    每滴水以每秒1个单位长度的速度下落。你需要把花盆放在x轴上的某个位置,使得从被花盆接着的第1滴水开始,到被花盆接着的最后1滴水结束,之间的时间差至少为D。

    我们认为,只要水滴落到x轴上,与花盆的边沿对齐,就认为被接住。给出N滴水的坐标和D的大小,请算出最小的花盆的宽度W。

    数据范围:

    40%的数据:1 ≤ N ≤ 1000,1 ≤ D ≤ 2000;

    100%的数据:1 ≤ N ≤ 100000,1 ≤ D ≤ 1000000,0≤x,y≤10^6。

    --------------------------------------------------我是分割线----------------------------------------------------

    题解:对于40%的数据,二分花盆的宽度,朴素O(N^2)检验宽度len能否满足条件,总时间复杂度O(N^2logN),期望得分40,实际得分50。

    #include<bits/stdc++.h>
    
    #define ll long long
    #define mp make_pair
    #define rep(i, a, b) for(int i = (a); i <= (b); i++) 
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    
    using namespace std;
    
    typedef pair<int, int> pii;
    typedef double db;
    const int N = 1e6 + 50;
    struct node{ int x, y; } a[N];
    int n, d, l = 1, r, ans = -1;
    inline int read(){
        int x = 0, f = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar();}
        while(ch >='0' && ch <='9') { x = (x<<3)+(x<<1)+(ch^48); ch = getchar();}
        return x*f;
    }
    bool mycmp(node a, node b){ return a.x < b.x; }
    void init(){
        n = read(); d = read();
        rep(i, 1, n) a[i].x = read(), a[i].y = read(), r = max(r, a[i].x);
        sort(a+1, a+n+1, mycmp);
    }
    bool check(int len){
        int dist = 0;
        rep(i, 1, n){
            int t = a[i].y, maxx = 0;
            maxx = max(maxx, a[i].y);
            rep(j, i+1, n){
                if(i == j) continue;
                if(a[j].x >= a[i].x && a[j].x <= a[i].x + len) {
                    maxx = max(maxx, a[j].y);
                    t = min(t, a[i].y);
                }
            }
            dist = max(dist, abs(maxx - t)); 
            if(dist >= d) return true;
        }
        return false;
    }
    void work(){
        while(l < r){
            int mid = (l+r) >> 1;
            if(check(mid)) r = mid, ans = mid;
            else l = mid + 1;
        }
        printf("%d
    ", ans);
    }
    int main(){
        init();
        work();
        return 0;
    }
    View Code

    考虑对朴素算法进行优化,我们发现,检验的过程max-min的值我们可以预处理维护的,于是把整条x轴看成一个区间,每个水滴的y值看成是区间下标的权值,于是我们就不难想到ST表,运用ST表预处理区间的最大值和最小值,于是可以O(N)检验。总时间复杂度O(NlogN),常数略大。

    #include<bits/stdc++.h>
    
    #define ll long long
    #define mp make_pair
    #define rep(i, a, b) for(int i = (a); i <= (b); i++) 
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    
    using namespace std;
    
    typedef pair<int, int> pii;
    typedef double db;
    const int N = 1e5 + 50;
    struct node{ int x, y; } a[N];
    int n, d, l = 1, r, ans = -1, maxn;
    int Max[N*10][21], Min[N*10][21];
    inline int read(){
        int x = 0, f = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar();}
        while(ch >='0' && ch <='9') { x = (x<<3)+(x<<1)+(ch^48); ch = getchar();}
        return x*f;
    }
    int ask_max(int l, int r){
        int k = log2(r-l+1);
        return max(Max[l][k], Max[r - (1<<k) + 1][k]);
    }
    int ask_min(int l, int r){
        int k = log2(r-l+1);
        return min(Min[l][k], Min[r - (1<<k) + 1][k]);
    }
    void init(){
        memset(Min, 0x3f, sizeof(Min));
        n = read(); d = read();
        rep(i, 1, n){
            a[i].x = read(), a[i].y = read(), maxn = max(maxn, a[i].x);
            Max[a[i].x][0] = max(Max[a[i].x][0], a[i].y);
            Min[a[i].x][0] = min(Min[a[i].x][0], a[i].y);
        }
        int t = log2(maxn);
        rep(j, 1, t) rep(i, 1, maxn - (1<<j) + 1) {
            Max[i][j] = max(Max[i][j-1], Max[i + (1<<(j-1))][j-1]);  
            Min[i][j] = min(Min[i][j-1], Min[i + (1<<(j-1))][j-1]);
        }
    }
    bool check(int len){
        rep(i, 1, maxn-len){
            if(ask_max(i, i+len) - ask_min(i, i+len) >= d) return true;
        }
        return false;
    }
    void work(){
        r = maxn;
        while(l <= r){
            int mid = (l+r) >> 1;
            if(check(mid)) r = mid-1, ans = mid;
            else l = mid + 1;
        }
        printf("%d
    ", ans);
    }
    int main(){
        init();
        work();
        return 0;
    }
    View Code

    这里再介绍一种新的维护方法,即动态维护区间最大值和最小值的基本套路。

    当我们二分到一个长度时,对于这个长度所能产生的最大的贡献即为max{max[i,i+len] - min[i,i+len] }。

    这时,我们就可以使用单调队列。

    维护两个单调队列,分别是单调不降和单调不升的队列,两个队头{min,max}即为所需求的答案。

    至于怎么维护,维护单调队列的基本形式,如果队头没有下一个元素优,那么就弹出队头,直到符合单调性为止,然后对队尾操作,使得队列包含的区间长度等于len+1。

    总时间复杂度O(NlogN),常数较小。

    #include<bits/stdc++.h>
    
    #define ll long long
    #define mp make_pair
    #define rep(i, a, b) for(int i = (a); i <= (b); i++)
    #define per(i, a, b) for(int i = (a); i >= (b); i--)
    
    using namespace std;
    
    typedef pair<int, int> pii;
    typedef double db;
    const int N = 1e6 + 50;
    struct node{ int x, y; } a[N];
    int n, d, maxx, ans = -1, b[N], k[N];
    struct npp{ int z, id; } ql[N], qh[N];
    inline int read(){
        int x = 0, f = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar();}
        while(ch >='0' && ch <='9') { x = (x<<3)+(x<<1)+(ch^48); ch = getchar();}
        return x*f;
    }
    void init(){
        memset(k, 0x3f, sizeof(k));
        n = read(); d = read();
        rep(i, 1, n) a[i].x = read(), a[i].y = read(), maxx = max(maxx, a[i].x);
        rep(i, 1, n) b[a[i].x] = max(b[a[i].x], a[i].y); 
        rep(i, 1, n) k[a[i].x] = min(k[a[i].x], a[i].y);
    }
    bool check(int len){
        len++;
        int ls = 1, rs = 0, lh = 1, rh = 0, sum = 0;
        rep(i, 1, maxx) {
            while(ls <= rs && k[i] <= ql[rs].z) rs--;
            while(lh <= rh && b[i] >= qh[rh].z) rh--;
            while(ql[ls].id <= i-len) ls++;
            while(qh[lh].id <= i-len) lh++;
            ql[++rs].z = k[i], ql[rs].id = i;
            qh[++rh].z = b[i], qh[rh].id = i;
            if(i >= len) sum = max(sum, qh[lh].z - ql[ls].z);
            if(sum >= d) return true;
        } 
        return false;
    }
    void work(){
        int l = 1, r = maxx;
        while(l <= r){
            int mid = (l+r) >> 1;
            if(check(mid)) r = mid-1, ans = mid;
            else l = mid+1;
        }
        printf("%d
    ", ans);
    }
    int main(){
        init();
        work();
        return 0;
    }
    View Code

     其实除了单调队列以外,动态维护区间最值还可以用STL的<set>,或者堆,不过时间复杂度要多一个log,可能会被卡常,推荐使用单调队列。

  • 相关阅读:
    ES6入门 阮一峰
    NPM
    移动端BUG
    配置每次git push 不需要输入账号密码
    移动端rem布局,用户调整手机字体大小或浏览器字体大小后导致页面布局出错问题
    课程表
    岛屿数量
    二叉树的右视图
    c++设计模式——工厂模式
    克隆图
  • 原文地址:https://www.cnblogs.com/smilke/p/11572809.html
Copyright © 2020-2023  润新知