• @bzoj



    @description@

    体育课上,n个小朋友排成一行(从1到n编号),老师想把他们分成若干组,每一组都包含编号连续的一段小朋友,每个小朋友属于且仅属于一个组。
    第i个小朋友希望它所在的组的人数不多于d[i],不少于c[i],否则他就会不满意。
    在所有小朋友都满意的前提下,求可以分成的组的数目的最大值,以及有多少种分组方案能达到最大值。

    原题传送门。

    @solution@

    (dp_i) 表示前 i 个人进行分组的最优值及方案数。我们可以从满足 (max{c_{j+1dots i}} leq i - j leq min{d_{j+1dots i}}) 的 j 转移到 i。
    朴素 dp 是 (O(n^2)) 的,需要优化。

    d 有单调性的:当 j 离 i 越远,(min{d_{j+1dots i}}) 越小同时 (i - j) 越大。
    我们可以记 (left_i) 表示只考虑 d 的限制,能够转移到 i 的最小 (j)

    不过,c 没有单调性。

    注意到对于区间 [l, r],如果 c 的最大值在 x,则从 [l, x-1] 转移到 [x, r] 的限制全部都是 c[x]。
    因此,我们可以考虑根据区间最大值(笛卡尔树)进行分治。

    不妨记 k = c[x]。此时如果 [l, x-1] 中的某个 j 可以转移到 [x, r] 中的某个 i,则应有 (left_i leq j leq i - k)。这是一个和 i 有关的区间,可以使用线段树维护。
    但是由于区间并不是等分两份然后递归,此时复杂度 (T(a + b) = T(a) + T(b) + blog n) 还是可能会炸。

    对于这种非等分的分治,我们可以考虑怎么把复杂度变为 (T(a + b) = T(a) + T(b) + min{a, b}log n),这样总时间复杂度就会降为 (O(nlog^2 n))(可以考虑成启发式合并的时间复杂度)。
    对于某个 j,它所能转移到的 i 也是个和 j 有关的区间,只需要根据 (left) 的单调性进行二分。这个线段树区间修改即可。
    左右区间哪个小,就在那个区间进行扫描 + 线段树查询/区间修改。这样就可以做到 (O(nlog^2 n)),但是还不足以通过本题。

    (left) 是一个很棘手的条件,我们考虑将 [x, r] 的 i 分成 3 种类型:(left_i < l)(l leq left_i < x)(x leq left_i),每一种都是连续的区间。

    第 3 种直接不管。
    第 2 种的 i 满足 (left_i < x leq i),也就是区间 ([left_i, i]) 横跨了分治中心。对于每个 i 只会有一次横跨,因此直接暴力 (O(log n)) 线段树查询总时间复杂度依然是 (O(nlog n))

    此时第 1 种就没有 (left) 的限制了,只需要满足 (j leq i - k)
    我们先对 x 求出满足 (j leq x - k) 的所有 j 的贡献(如果左区间小就暴力扫;如果右区间小就线段树查)。
    此时如果 i 变为 i + 1,则最多只会贡献一个新的 j;同理如果 j 变为 j + 1,则转移范围最多少 1。因此直接扫描一遍,就不需要线段树。
    最后如果左区间小,则还需要把那些取值范围覆盖整个左区间的点 i 线段树区间修改。

    第 1 种的复杂度为 (T(a + b) = T(a) + T(b) + min{a, b} + log n),前面依然通过启发式合并的想法得到复杂度 (O(nlog n));而后面的 (log n) 的贡献是决策树内的点数,由于决策树大小为 O(n) 所以总复杂度为 (O(nlog n))

    因此总时间复杂度 (O(nlog n))

    @accepted code@

    #include <cstdio>
    #include <algorithm>
    using namespace std;
     
    const int MOD = 1000000007;
    const int MAXN = 1000000;
    const int INF = (1 << 30);
     
    struct type{
        int mx, cnt; type() : mx(-INF) {}
        type(int _m, int _c) : mx(_m), cnt(_c) {}
        friend type operator + (const type &a, const type &b) {
            if( a.mx < 0 ) return b;
            else if( b.mx < 0 ) return a;
            else {
                if( a.mx == b.mx )
                    return type(a.mx, a.cnt + b.cnt >= MOD ? a.cnt + b.cnt - MOD : a.cnt + b.cnt);
                else return (a.mx > b.mx ? a : b);
            }
        }
    };
    type func(type x) {return x.mx >= 0 ? type(x.mx + 1, x.cnt) : type();}
     
    namespace segtree{
        type tag[2*MAXN + 5], mx[2*MAXN + 5];
         
        inline int id(int l, int r) {return (l + r) | (l != r);}
        void maintain(int x, type t) {tag[x] = tag[x] + t, mx[x] = mx[x] + t;}
        void pushup(int l, int r) {
            int m = (l + r) >> 1;
            int x = id(l, r), lch = id(l, m), rch = id(m + 1, r);
            mx[x] = mx[lch] + mx[rch];
        }
        void pushdown(int l, int r) {
            int m = (l + r) >> 1;
            int x = id(l, r), lch = id(l, m), rch = id(m + 1, r);
            if( tag[x].mx >= 0 ) {
                maintain(lch, tag[x]);
                maintain(rch, tag[x]);
                tag[x] = type();
            }
        }
        void update(int l, int r, int ql, int qr, type k) {
            int x = id(l, r);
            if( ql > r || qr < l ) return ;
            if( ql <= l && r <= qr ) {
                maintain(x, k);
                return ;
            }
            pushdown(l, r);
            int m = (l + r) >> 1;
            update(l, m, ql, qr, k);
            update(m + 1, r, ql, qr, k);
            pushup(l, r);
        }
        type query(int l, int r, int ql, int qr) {
            int x = id(l, r);
            if( ql > r || qr < l ) return type();
            if( ql <= l && r <= qr ) return mx[x];
            pushdown(l, r); int m = (l + r) >> 1;
            return query(l, m, ql, qr) + query(m + 1, r, ql, qr);
        }
    };
     
    int read() {
        int x = 0; char ch = getchar();
        while( '0' > ch || ch > '9' ) ch = getchar();
        while( '0' <= ch && ch <= '9' ) x = 10*x + ch - '0', ch = getchar();
        return x;
    }
     
    int c[MAXN + 5], d[MAXN + 5], n;
     
    int ch[2][MAXN + 5], stk[MAXN + 5], tp, rt;
    void get() {
        for(int i=1;i<=n;i++) {
            int nw = 0;
            while( tp && c[stk[tp]] <= c[i] )
                ch[1][stk[tp]] = nw, nw = stk[tp--];
            stk[++tp] = i, ch[0][i] = nw;
        }
        while( tp ) {
            int x = stk[tp--];
            if( tp ) ch[1][stk[tp]] = x;
            else rt = x;
        }
    }
     
    int le[MAXN + 5]; type dp[MAXN + 5];
    void solve(int l, int r, int x) {
    //  printf("%d %d %d
    ", l, r, x);
        if( l == r ) {
            segtree::update(0, n, l, r, dp[l]);
            dp[l] = segtree::query(0, n, l, r);
            return ;
        }
        else {
            solve(l, x - 1, ch[0][x]);
             
            int k = c[x];
            int p = lower_bound(le + x, le + r + 1, l) - le;
            int q = lower_bound(le + x, le + r + 1, x) - le;
             
            if( x - l < r - x + 1 ) {
                type nw = type();
                for(int i=l;i<x;i++) {
                    nw = nw + func(dp[i]);
                    if( x <= i + k && i + k < p )
                        dp[i + k] = dp[i + k] + nw;
                }
                segtree::update(0, n, x + k, p - 1, nw);
            }
            else {
                type nw = func(segtree::query(0, n, l, x - k - 1));
                for(int i=x;i<p;i++) {
                    if( l <= i - k && i - k < x )
                        nw = nw + func(dp[i - k]);
                    dp[i] = dp[i] + nw;
                }
            }
             
            for(int i=p;i<q;i++)
                dp[i] = dp[i] + func(segtree::query(0, n, le[i], min(i - k, x - 1)));
             
            solve(x, r, ch[1][x]);
        }
    }
    int main() {
        n = read();
        for(int i=1;i<=n;i++)
            c[i] = read(), d[i] = read();
         
        int s, t; stk[s = t = 1] = 1, le[1] = 0;
        for(int i=2;i<=n;i++) {
            while( s <= t && d[i] <= d[stk[t]] )
                t--;
            stk[++t] = i;
             
            le[i] = le[i - 1];
            while( i - le[i] > d[stk[s]] ) {
                le[i]++;
                while( stk[s] <= le[i] ) s++;
            }
        }
         
        dp[0] = type(0, 1), get(), solve(0, n, rt);
        if( dp[n].mx >= 0 ) printf("%d %d
    ", dp[n].mx, dp[n].cnt);
        else puts("NIE");
    }
    

    @details@

    本题卡空间,所以你需要一些卡空间的手段(比如线段树空间从 4n 降到 2n 的方法)。

  • 相关阅读:
    PHP 单态设计模式
    五中常见的PHP设计模式
    PHP如何定义类及其成员属性与操作
    thinkphp 中MVC思想
    1.4 算法
    1.3 迭代器
    1.2 容器-container
    1.1 STL 概述
    2.3顺序容器-deque
    2.2 顺序容器-list
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/12626963.html
Copyright © 2020-2023  润新知