CF1470E 题解 —— 询问分叉转构建虚树的复杂度证明
阅读原文时间:2023年07月10日阅读:2

简要题意:给定一个长为 \(n\) 的排列 \(p\) 和一个整数 \(c\le 4\),称排列 \(p'\) 合法当且仅当 \(p'\) 可以通过 \(p\) 翻转若干个不交的区间 \([l,r]\) 得到,并且这些区间的长度和 \(r-l\le c\)。\(Q\) 次询问所有合法的 \(p'\) 中字典序第 \(x\) 小的第 \(y\) 个位置的值。\(n,Q\le 3\times 10^5,c\le 4\)。

考虑每个询问的结果 \(p'\) 和原排列 \(p\) 至多相差 \(c\) 个位置,考虑从这里入手。

将所有询问按 \(x\) 排序,考虑这样一个过程:定义 \(solve(u,c,rk,L,R)\) 表示当前已经确定了 \(p'\) 前 \(u\) 项的位置,当前还剩下 \(c\) 的交换次数可用,前 \(u\) 项已经加的排名量为 \(rk\),只需处理 \(L,R\) 内的询问。在 \(solve\) 中,我们将 \(a_u\sim a_{u+c}\) 排序,然后分别从左往右和从右往左拿一个指针扫一遍询问数组,将 \(a_u\) 这个位置会改变的询问拎出来递归处理,最后剩下一些 \(a_u\) 这个位置不改变的直接递归处理(注意这些询问不用花时间扫一遍)。在递归处理时,我们首先在序列上二分出询问 \([L,R]\) 中第一个有任意一个询问会改变的位置,将 \(u\) 跳到那个位置再开始处理。至于如何迅速求出一个点 \(u\) 往后至多花 \(c\) 的长度翻转的方案数,发现这等价于 \(u\sim n\) 一共 \(n-u\) 个空位中选出至多 \(c\) 个空位放横杠的方案数,可以 \(O(1)\) 计算。

看一下这样做的时间复杂度为什么是对的。首先关于扫询问那部分的复杂度,由于每扫过一个询问就意味着该询问在该位置上改变了一下,而所有询问的总改变次数是 \(O(Qc)\) 的,所以这部分扫的时间复杂度为 \(O(Qc)\)。重点在于递归次数的复杂度分析,很有借鉴意义。考虑所有可能的操作序列形成一个类似 \(c\) 叉树的结构(尽管有些节点可能不足 \(c\) 个儿子),现在的询问相当于询问第 \(x\) 个叶子的信息。如果前面递归下去时不二分,那么这个过程就相当于将这些叶子形成的“虚树”拎出来,保留“虚树”的每条边上原树的若干节点,得到的“虚树”大小,这显然是 \(O(Qn)\) 的。但是,我们的二分操作相当于跳过了所有“虚树”上不分叉的位置,也就是相当于取出了一棵真正的只保留 LCA 的虚树!于是 \(solve\) 操作只会调用 \(O(Q)\) 次!

时间复杂度 \(O(nc^2+Qc+Q\log n+sort(Q))\)。

代码如下:

#include "bits/stdc++.h"
#define For(i, a, b) for (int i = a; i <= b; i++)
#define Rev(i, a, b) for (int i = a; i >= b; i--)
#define Fin(file) freopen(file, "r", stdin)
#define Fout(file) freopen(file, "w", stdout)
#define assume(expr) ((!!(expr)) || (exit((fprintf(stderr, "Assumption Failed: %s on Line %d\n", #expr, __LINE__), -1)), false))
using namespace std;
const int N = 3e5 + 5;
typedef long long ll;
struct Query
{
    ll x, y;
    int id;
    bool operator<(const Query &qry) const { return x < qry.x; }
} qry[N];
int n, CC, Q, a[N], ans[N];
ll _Get[N][5];
ll ssum[N][5];
vector<int> nxt[N];
inline ll C(ll x, int y)
{
    if (y < 0 || x < y)
        return 0;
    ll res = 1;
    For(i, 1, y) res = res * (x - i + 1) / i;
    return res;
}
inline ll __Get(int i, int j)
{
    if (i == n + 1)
        return 1;
    ll res = 0;
    For(k, 0, j) res += C(n - i, k);
    return res;
}
inline ll Get(int i, int j) { return _Get[i][j]; }
bool check(int u, int v, int c, ll rk, ll x)
{
    rk += ssum[v][c] - ssum[u - 1][c];
    bool res = rk < x && x <= rk + Get(v + 1, c);
    return res;
}
void solve(int u, int c, ll rk, int L, int R);
void work(int u, int c, ll rk, int L, int R)
{
    if (L > R)
        return;
    int l = u, r = n + 1;
    while (l < r)
    {
        int mid = (l + r) >> 1;
        if (check(u, mid, c, rk, qry[L].x) && check(u, mid, c, rk, qry[R].x))
            l = mid + 1;
        else
            r = mid;
    }
    solve(l, c, rk + ssum[l - 1][c] - ssum[u - 1][c], L, R);
}
void solve(int u, int c, ll rk, int L, int R)
{
    if (L > R)
        return;
    if (u == n + 1 || c == 0)
    {
        For(i, L, R) ans[qry[i].id] = a[qry[i].y];
        return;
    }
    int sz = nxt[u].size(), pp = L;
    ll cur = rk;
    For(ooo, 0, sz - 1)
    {
        int c0 = nxt[u][ooo];
        if (c0 == 0)
            break;
        else if (c0 > c || u + c0 > n)
            continue;
        int lst = pp;
        while (pp <= R && qry[pp].x <= cur + Get(u + c0 + 1, c - c0))
            pp++;
        reverse(a + u, a + u + c0 + 1);
        work(u + c0 + 1, c - c0, cur, lst, pp - 1);
        cur += Get(u + c0 + 1, c - c0);
        reverse(a + u, a + u + c0 + 1);
    }
    int PosL = pp;
    pp = R;
    cur = rk + Get(u, c);
    Rev(ooo, sz - 1, 0)
    {
        int c0 = nxt[u][ooo];
        if (c0 == 0)
            break;
        else if (c0 > c || u + c0 > n)
            continue;
        cur -= Get(u + c0 + 1, c - c0);
        int lst = pp;
        while (pp >= L && qry[pp].x > cur)
            pp--;
        reverse(a + u, a + u + c0 + 1);
        work(u + c0 + 1, c - c0, cur, pp + 1, lst);
        reverse(a + u, a + u + c0 + 1);
    }
    cur -= Get(u + 1, c);
    work(u + 1, c, cur, PosL, pp);
}
void Solve()
{
    cin >> n >> CC >> Q;
    For(i, 1, n) cin >> a[i];
    For(i, 1, n + 1) For(j, 0, CC) _Get[i][j] = __Get(i, j);
    For(i, 1, n)
    {
        nxt[i].clear();
        For(j, 0, min(CC, n - i)) nxt[i].push_back(j);
        sort(nxt[i].begin(), nxt[i].end(), [&](int x, int y)
             { return a[i + x] < a[i + y]; });
        int sz = nxt[i].size();
        For(c, 0, CC)
        {
            ssum[i][c] = ssum[i - 1][c];
            For(ooo, 0, sz - 1)
            {
                int c0 = nxt[i][ooo];
                if (c0 == 0)
                    break;
                else if (c0 > c || i + c0 > n)
                    continue;
                ssum[i][c] += Get(i + c0 + 1, c - c0);
            }
        }
    }
    int qcnt = 0;
    For(i, 1, Q)
    {
        ll x, y;
        cin >> y >> x;
        if (x > Get(1, CC))
            ans[i] = -1;
        else
            qry[++qcnt] = {x, y, i};
    }
    sort(qry + 1, qry + 1 + qcnt);
    solve(1, CC, 0, 1, qcnt);
    For(i, 1, Q)
    {
        if (ans[i] >= 0)
            cout << ans[i] << '\n';
        else
            cout << "-1\n";
    }
    For(i, 1, n + 1) For(j, 0, CC) _Get[i][j] = 0;
}
signed main()
{
    int T;
    cin >> T;
    while (T--)
        Solve();
    cerr << "Time = " << clock() << " ms" << endl;
    return 0;
}

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章