简要题意:给定一个长为 \(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;
}
手机扫一扫
移动阅读更方便
你可能感兴趣的文章