Tsinsen D486 蓝雨

传送门
题目大意

给定一个长度为 n ( 1 n 10 5 ) 的正整数序列 a ,满足 1 a i n 。现在规定这个序列中的两个区间可以比较大小,比较方式为:将区间中 1 n 的出现次数依次记下,得到一个长度为 n 的“字符串”,规定字典序大的那个区间更大。如果通过这种方式比较发现两个区间大小相同,那么左端点较小的那个区间更大。

求第 p 大到第 q 的区间( 1 q p + 1 10 5 )。时限 3s。

对于 n 1000 的子任务

考虑暴力对所有区间排序。于是我们立刻得到一个 O ( n 2 log 2 n ) 的做法:用主席树保存区间 [ 1 , i ] 的“字符串”,设为 s [ 1 , i ] ,则 s [ j , i ] = s [ 1 , i ] s [ 1 , j 1 ] ,其中减号表示每一位都相减。特别地, s [ 1 , 0 ] 全是 0

我们在主席树中计算出区间的哈希值,则我们可以用哈分加二希在 O ( log n ) 的时间复杂度内比较两个区间。因此总时间复杂度是 O ( n 2 log 2 n ) 的。能够得到 42 分。

对于 p = 1 的情况

可以发现这么一个性质:对于区间 [ l , r 1 ] [ l , r 2 ] r 1 < r 2 ),一定有 s [ l , r 1 ] < s [ l , r 2 ] 。因此我们立即得到一个 O ( n log 2 n ) 的做法:使用堆保存每个区间,取出区间时将区间右端点缩小 1 重新放入堆中即可。能够得到 0 分 QAQ。

对于 100 % 的数据

发现,如果我们知道了第 p 大的区间,那么问题就解决了:用上面的做法即可。

我们考虑逐位确定第 p 大区间对应的“字符串”,这样我们就可以知道对于每个左端点,第 p 大区间的右端点出现在哪个范围内。对于每一种数,我们把它们的位置单独保存下来。

在前 i 位确定的情况下,对于一个左端点而言使得这个区间的 i 位等于当前的字符串的右端点呈一个区间,我们用数据结构把每个左端点的右端点的可行区间存下来,表示只看前 i 位时使得字符串等于我们计算的答案时的右端点的区间。当我们开始二分第 i + 1 位时,我们计算 i 位相同,第 i + 1 大于等于二分的答案时的个数。对于某个左端点而言,满足条件的右端点不仅要在先前计算的区间之内,还要保证至少有二分的答案个 i + 1 :这样的限制又是一个区间,并且这个区间只有左端点有意义,右端点可以看作 n 。在得到答案后,我们把 p 减去第 i + 1 大于答案的个数:因为它们之后始终都比我们计算的那个字符串大(这也是为什么前面说要保存等于的而不是大于等于的)。这样下来,我们最后求出的就是等于第 P 个区间的字符串了。

考虑实现,我们用一棵线段树来计算。理论上,线段树的叶结点表示的是以原序列的这个位置为左端点时右端点能取哪个区间使得前 i 位与已经确定的字符串的前 i 位相同。我们要查询的内容是线段树上一个区间内所有叶结点表示的区间的区间长度总和(注意两个区间的区别),还要求统计的答案中不包含“右端点小于给定值的区间”。我们要修改的内容是:对线段树上一个区间内的所有区间的所有左端点 checkmax,所有右端点 checkmin(同样注意两个区间的区别)。

显然我们不能只保存区间长度。由于太复杂,我自己都想不到,因此我只好直接告诉你要保存什么了:对于每个线段树上的结点,要保存叶结点代表的区间左端点最小值 l1,左端点最大值 l2,右端点最小值 r1,右端点最大值 r2,左端点之和 sl,右端点之和 sr,叶结点表示的区间的长度至少为 1 的区间个数(简称有效区间个数) sx(显然对于一个叶结点,sx 要么为 0,要么为 1,为 1 时该叶结点的 l1(l2) <= r1(r2))。

考虑查询。若当前线段树的区间被查询区间完全覆盖,并且这个区间内的叶结点的所有区间的最小左端点都大于等于那个限制,我们直接返回右端点之和减去左端点之和加上有效区间个数。(规定无效区间的左右端点都置零);如果最大右端点都小于那个限制,我们直接返回 0 。如果所有左端点小于等于那个限制,所有右端点大于等于那个限制,我们就把那个限制作为所有区间的左端点即可。剩下的我们暴力下去算。

考虑修改。(看代码吧……)如果能够打标记就打标记,否则暴力下去算。

考虑 pushdown,我们把标记直接放到 l1r2 上。(其实我也不懂为什么)

所以有没有犇犇教教我:为什么要这么做呢?这么做的时间复杂度为什么是对的呢?


考虑如何计算第 p 大区间。显然的是,现在每个左端点要么有一个右端点恰好大于第 p 大区间,要么没有右端点大于第 p 大区间,这个数正好等于 sx。我们从左往右检查即可(注意到,剩下的都是大于等于 p 大的,大于第 p 大的已经在 p 中减去了,因此从左往右看即可)。


剩下的东西简单得一比,直接放进堆里欢快地运行,用前面说的主席树进行比较即可。时间复杂度 O ( n log 2 n )

参考代码
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cassert>
#include <cctype>
#include <climits>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
#include <list>
#include <functional>
using LL = long long;
using ULL = unsigned long long;
using std::cin;
using std::cout;
using std::endl;
using INT_PUT = LL;
INT_PUT readIn()
{
    INT_PUT a = 0;
    bool positive = true;
    char ch = getchar();
    while (!(std::isdigit(ch) || ch == '-')) ch = getchar();
    if (ch == '-')
    {
        positive = false;
        ch = getchar();
    }
    while (std::isdigit(ch))
    {
        (a *= 10) -= ch - '0';
        ch = getchar();
    }
    return positive ? -a : a;
}
void printOut(INT_PUT x)
{
    char buffer[20];
    int length = 0;
    if (x < 0) putchar('-');
    else x = -x;
    do buffer[length++] = -(x % 10) + '0'; while (x /= 10);
    do putchar(buffer[--length]); while (length);
}

const int maxn = int(1e5) + 5;
int n;
LL p, q;
int a[maxn];
int bound;
int disc[maxn];
void discretize()
{
    for (int i = 1; i <= n; i++)
        disc[i] = a[i];
    std::sort(disc + 1, disc + 1 + n);
    bound = std::unique(disc + 1, disc + 1 + n) - (disc + 1);
    for (int i = 1; i <= n; i++)
        a[i] = std::lower_bound(disc + 1, disc + 1 + bound, a[i]) - disc;
}

class FuncSegTree
{
    int size = 0;
    struct Node
    {
        ULL hash;
        int lc, rc;
        Node() : hash(), lc(), rc() {}
    } nodes[maxn * 20];
    std::vector<int> versions;

    int bound;
    ULL power[maxn];

    int g_Pos;
    void insert(int& node, int l, int r, int src)
    {
        if (!(l <= g_Pos && g_Pos <= r))
        {
            node = src;
            return;
        }
        node = size++;
        if (l == r)
        {
            nodes[node].hash = nodes[src].hash + 1;
            return;
        }
        int mid = (l + r) >> 1;
        insert(nodes[node].lc, l, mid, nodes[src].lc);
        insert(nodes[node].rc, mid + 1, r, nodes[src].rc);
        nodes[node].hash = nodes[nodes[node].lc].hash * power[r - mid] +
            nodes[nodes[node].rc].hash;
    }

public:
    FuncSegTree()
    {
        versions.push_back(size++);
        power[0] = 1;
        for (int i = 1; i <= int(1e5); i++)
            power[i] = power[i - 1] * 1313131;
    }
    void SetBound(int b) { bound = b; }
    void clone()
    {
        versions.push_back(versions.back());
    }
    void insert(int pos)
    {
        g_Pos = pos;
        insert(versions.back(), 1, bound, versions.back());
    }
    bool comp(int l1, int r1, int l2, int r2) const
    {
        bool temp = l1 > l2;
        if (l1 == 2 && r1 == 11 && l2 == 1 && r2 == 10)
            int a = 0;
        if (l2 == 2 && r2 == 11 && l1 == 1 && r1 == 10)
            int a = 0;
        l1 = versions[l1 - 1];
        r1 = versions[r1];
        l2 = versions[l2 - 1];
        r2 = versions[r2];
        int l = 1, r = bound;
        while (r - l > 0)
        {
            int mid = (l + r) >> 1;
            if (nodes[nodes[r1].lc].hash - nodes[nodes[l1].lc].hash ==
                nodes[nodes[r2].lc].hash - nodes[nodes[l2].lc].hash)
            {
                l = mid + 1;
                l1 = nodes[l1].rc;
                r1 = nodes[r1].rc;
                l2 = nodes[l2].rc;
                r2 = nodes[r2].rc;
            }
            else
            {
                r = mid;
                l1 = nodes[l1].lc;
                r1 = nodes[r1].lc;
                l2 = nodes[l2].lc;
                r2 = nodes[r2].lc;
            }
        }
        if (nodes[r1].hash - nodes[l1].hash !=
            nodes[r2].hash - nodes[l2].hash)
            return nodes[r1].hash - nodes[l1].hash <
            nodes[r2].hash - nodes[l2].hash;
        return temp;
    }
};
FuncSegTree fst;
class SegTree
{
    struct Node
    {
        int l1, l2, r1, r2; // 最小、最大左端点,最小、最大右端点
        LL sl, sr; // 左端点之和,右端点之和
        int sx; // l <= r 的个数
    } nodes[maxn * 2];
    static inline int code(int l, int r)
    {
        return (l + r) | (l != r);
    }

    void update(int l, int r)
    {
        Node& t = nodes[code(l, r)];
        int mid = (l + r) >> 1;
        Node& lc = nodes[code(l, mid)];
        Node& rc = nodes[code(mid + 1, r)];
        t.l1 = std::min(lc.l1, rc.l1);
        t.l2 = std::max(lc.l2, rc.l2);
        t.r1 = std::min(lc.r1, rc.r1);
        t.r2 = std::max(lc.r2, rc.r2);
        t.sl = lc.sl + rc.sl;
        t.sr = lc.sr + rc.sr;
        t.sx = lc.sx + rc.sx;
    }
    void pushdown(int l, int r)
    {
        Node& t = nodes[code(l, r)];
        int mid = (l + r) >> 1;
        Node& lc = nodes[code(l, mid)];
        Node& rc = nodes[code(mid + 1, r)];
        if (t.l1 > lc.l2) // 不合常理,说明是从上往下的标记
        {
            lc.sl = (LL)t.l1 * lc.sx;
            lc.l1 = lc.l2 = t.l1;
        }
        if (t.l1 > rc.l2)
        {
            rc.sl = (LL)t.l1 * rc.sx;
            rc.l1 = rc.l2 = t.l1;
        }
        if (t.r2 < lc.r1)
        {
            lc.sr = (LL)t.r2 * lc.sx;
            lc.r1 = lc.r2 = t.r2;
        }
        if (t.r2 < rc.r1)
        {
            rc.sr = (LL)t.r2 * rc.sx;
            rc.r1 = rc.r2 = t.r2;
        }
    }

    int g_L, g_R, g_Pos, g_Val, g_Val2;
    LL query_(int l, int r)
    {
        Node& t = nodes[code(l, r)];
        if (!t.sx) return 0;
        if (g_L <= l && r <= g_R)
        {
            if (t.l1 >= g_Pos)
                return t.sr - t.sl + t.sx;
            if (t.r2 < g_Pos)
                return 0;
            if (t.r1 >= g_Pos && t.l2 <= g_Pos)
                return t.sr - (LL)t.sx * (g_Pos - 1);
        }
        pushdown(l, r);
        int mid = (l + r) >> 1;
        LL ret = 0;
        if (g_L <= mid) ret += query_(l, mid);
        if (g_R > mid) ret += query_(mid + 1, r);
        return ret;
    }
    void modify_(int l, int r)
    {
        Node& t = nodes[code(l, r)];
        if (!t.sx) return;
        if (g_L <= l && r <= g_R)
        {
            bool bOk = true;
            if (t.r2 < g_Val || t.l1 > g_Val2)
                t.sx = t.sl = t.sr = 0;
            else if (t.l1 >= g_Val && t.r2 <= g_Val2) {}
            else if (t.l2 <= g_Val && t.r1 >= g_Val && t.r2 <= g_Val2)
                t.sl = (LL)g_Val * t.sx;
            else if (t.l1 >= g_Val && t.l2 <= g_Val2 && t.r1 >= g_Val2)
                t.sr = (LL)g_Val2 * t.sx;
            else if (t.l2 <= g_Val && g_Val2 <= t.r1)
            {
                t.sl = (LL)g_Val * t.sx;
                t.sr = (LL)g_Val2 * t.sx;
            }
            else
                bOk = false;

            if (bOk)
            {
                t.l1 = std::max(t.l1, g_Val);
                t.l2 = std::max(t.l2, g_Val);
                t.r1 = std::min(t.r1, g_Val2);
                t.r2 = std::min(t.r2, g_Val2);
                return;
            } // 否则暴力下去改
        }
        pushdown(l, r);
        int mid = (l + r) >> 1;
        if (g_L <= mid) modify_(l, mid);
        if (g_R > mid) modify_(mid + 1, r);
        update(l, r);
    }

public:
    void build(int l, int r)
    {
        if (l == r)
        {
            Node& t = nodes[code(l, r)];
            t.l1 = t.l2 = l;
            t.r1 = t.r2 = n;
            t.sl = l;
            t.sr = n;
            t.sx = 1;
            return;
        }
        int mid = (l + r) >> 1;
        build(l, mid);
        build(mid + 1, r);
        update(l, r);
    }
    // 查询左端点在 [l, r],在满足前 i 位等于答案时(在区间内时)且第 i + 1 位大于等于答案时(从 least 到 n 时)区间总个数
    LL query(int l, int r, int least)
    {
        g_L = l;
        g_R = r;
        g_Pos = least;
        return query_(1, n);
    }
    // 使左端点在 [l, r] 的右端点区间的左端点至少为 valL,右端点至多为 valR
    void modify(int l, int r, int valL, int valR)
    {
        g_L = l;
        g_R = r;
        g_Val = valL;
        g_Val2 = valR;
        modify_(1, n);
    }

private:
    // 计算出区间的左右端点
    std::pair<int, int> ret;
    void wander(int l, int r)
    {
        Node& t = nodes[code(l, r)];
        if (t.sx < p)
        {
            p -= t.sx;
            return;
        }
        if (l == r)
        {
            p--;
            ret = std::make_pair(l, t.r2);
            return;
        }
        pushdown(l, r);
        int mid = (l + r) >> 1;
        wander(l, mid);
        if (!p) return;
        wander(mid + 1, r);
    }
public:
    std::pair<int, int> wander()
    {
        wander(1, n);
        return ret;
    }
};

#define RunInstance(x) delete new x
struct brute
{
    std::vector<std::pair<int, int>> pairs;

    brute()
    {
        fst.SetBound(n);
        for (int i = 1; i <= n; i++)
        {
            fst.clone();
            fst.insert(a[i]);
        }
        for (int i = 1; i <= n; i++)
            for (int j = i; j <= n; j++)
            {
                pairs.push_back(std::make_pair(i, j));
            }

        std::partial_sort(pairs.begin(), pairs.begin() + q, pairs.end(),
            [&](const std::pair<int, int>& a, const std::pair<int, int>& b)
        {
            return fst.comp(b.first, b.second, a.first, a.second);
        });

        for (int i = p - 1; i < q; i++)
        {
            printOut(pairs[i].first);
            putchar(' ');
            printOut(pairs[i].second);
            putchar('\n');
        }
    }
};
struct work
{
    std::vector<std::vector<int>> poss;

    struct HeapNode
    {
        int l, r;
        HeapNode() = default;
        HeapNode(int l, int r) : l(l), r(r) {}
        bool operator<(const HeapNode& b) const
        {
            return fst.comp(l, r, b.l, b.r);
        }
    };

    SegTree st;
    LL check(int num, int s)
    {
        if (s > poss[num].size() - 1)
            return 0;
        LL ret = 0;
        for (int i = 0, size = poss[num].size(); i < size; i++)
        {
            int L = (i ? poss[num][i - 1] : 0) + 1;
            int R = std::min(poss[num][i], n);
            if (L > R || L > n)
                continue;
            if (i + s - 1 >= size - 1)
                break;
            int least;
            if (i + s - 1 < 0)
                least = 1;
            else
                least = poss[num][i + s - 1];
            if (L <= R)
                ret += st.query(L, R, least);
        }
        return ret;
    }

    int sel[maxn];

    work()
    {
        poss.resize(bound + 1);
        for (int i = 1; i <= n; i++)
            poss[a[i]].push_back(i);

        q = q - p + 1;
        std::priority_queue<HeapNode> pq;
        st.build(1, n);
        for (int i = 1; i <= bound; i++)
        {
            poss[i].push_back(n + 1);

            int l = 0, r = poss[i].size();
            while (r - l > 1)
            {
                int mid = (l + r) >> 1;
                if (check(i, mid) < p)
                    r = mid;
                else
                    l = mid;
            }
            if (l < poss[i].size() - 1)
                p -= check(i, l + 1); // 减去包含了大于 l 个 i 的区间
            sel[i] = l;
            for (int j = 0, size = poss[i].size(); j < size; j++)
            {
                int L = (j ? poss[i][j - 1] : 0) + 1;
                int R = std::min(poss[i][j], n);
                if (L > R || L > n)
                    continue;

                int valL, valR;
                if (j + l - 1 < 0)
                    valL = 1;
                else if (j + l - 1 >= size - 1)
                    valL = n + 1;
                else
                    valL = poss[i][j + l - 1];

                if (j + l >= size - 1)
                    valR = n;
                else
                    valR = poss[i][j + l] - 1;

                st.modify(L, R, valL, valR);
            }
        }

        fst.SetBound(n);
        for (int i = 1; i <= n; i++)
        {
            fst.clone();
            fst.insert(a[i]);
        }

        std::pair<int, int> pth = st.wander();
        HeapNode node(pth.first, pth.second);
        for (int i = 1; i <= n; i++)
            if (i != node.l)
            {
                int l = i - 1, r = n + 1;
                while (r - l > 1)
                {
                    int mid = (l + r) >> 1;
                    if (node < HeapNode(i, mid))
                        r = mid;
                    else
                        l = mid;
                }
                if (l >= i)
                    pq.push(HeapNode(i, l));
            }
        pq.push(node);

        for (LL i = 1; i <= q; i++)
        {
            HeapNode t = pq.top();
            pq.pop();
            if (t.l != t.r)
                pq.push(HeapNode(t.l, t.r - 1));

            printOut(t.l);
            putchar(' ');
            printOut(t.r);
            putchar('\n');
        }
    }
};

void run()
{
    n = readIn();
    p = readIn();
    q = readIn();
    for (int i = 1; i <= n; i++)
        a[i] = readIn();
    discretize();

    RunInstance(work);
}

int main()
{
    run();
    return 0;
}
另一个优秀的做法

问题还是确定第 p 大区间是哪一个。前面是从区间表示的“字符串”入手的,有没有别的方法呢?

我们考虑随机这个区间是哪个——当然不能纯随机了。显然的是每个左端点能够选的右端点还是一个区间,于是我们随机区间内的即可。随机出来一个可能的答案后,去更新所有左端点能够选的右端点(用二分就知道随机出的答案是比第 k 大大还是小,然后再用二分把每个左端点对应的右端点不可能成为第 k 大的排除掉),这个过程的时间复杂度是 O ( n log 2 n ) 的。期望每次能够把有效区间总长度减少一半,因此整个过程的时间复杂度为 O ( n log 3 n ) 。距反馈,这个算法的实际表现不算太糟,还是有跑过的机会的,关键是比上面那个算法好懂多了。(虽然我没写)

猜你喜欢

转载自blog.csdn.net/lycheng1215/article/details/80802909