简要题意:给定一个长为 \(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;
}

CF1470E 题解 —— 询问分叉转构建虚树的复杂度证明的更多相关文章

  1. 虚树总结&题单&简要题解

    简介 虚树,即剔除所有无关结点,只保留询问点和询问点的相关结点(两两之间的LCA),建一棵新树,这棵新树就是虚树.通过虚树,可以有效的减小询问(甚至修改)的复杂度.设询问点的个数是\(k\),那么建虚 ...

  2. bzoj 2286 [Sdoi2011]消耗战(虚树+树上DP)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=2286 [题意] 给定一棵树,切断一条树边代价为ci,有m个询问,每次问使得1号点与查询 ...

  3. [SDOI2011]消耗战(虚树)

    洛古题面 题意:给定一棵树,割断每一条边都有代价,每次询问会给定一些点,求用最少的代价使所有给定点都和1号节点不连通 暴力\(DP\) 我们先考虑暴力怎么做 设\(dp[u]\)为以\(u\)为根的子 ...

  4. 【WC2018】通道(边分治,虚树,动态规划)

    [WC2018]通道(边分治,虚树,动态规划) 题面 UOJ 洛谷 题解 既然是三棵树,那么显然就是找点什么东西来套个三层. 一棵树怎么做?入门dp. 两棵树?假设在第一棵树中的深度为\(dep\). ...

  5. 洛谷P3233 世界树 [HNOI2014] 虚树

    正解:虚树 解题报告: 传送门! 首先看到这种就要想到虚树这个是毫无疑问的QwQ 建虚树什么的都可以循规蹈矩地做,不说辣,具体可以看下虚树学习笔记什么的看下板子 但是建好虚树之后怎么搞还是有点儿讲究, ...

  6. [CF966F]May Holidays[分块+虚树]

    题意 给定 \(n\) 个点的树,初始所有颜色都是 \(0\) ,每个点有一个阈值 \(t\) ,每次可能会让一个点的颜色异或1,问每次操作之后有多少个点满足子树内的颜色为 \(1\) 的点的个数 \ ...

  7. 2018.09.25 bzoj3572: [Hnoi2014]世界树(虚树+树形dp)

    传送门 虚树入门题? 好难啊. 在学习别人的写法之后终于过了. 这道题dp方程很好想. 主要是不好写. 简要说说思路吧. 显然最优值只能够从子树和父亲转移过来. 于是我们先dfs一遍用儿子更新父亲,然 ...

  8. 【CF613D】Kingdom and its Cities(虚树,动态规划)

    [CF613D]Kingdom and its Cities(虚树,动态规划) 题面 洛谷 CF 翻译洛谷上有啦 题解 每次构建虚树,首先特判无解,也就是关键点中存在父子关系. 考虑\(dp\),设\ ...

  9. 【BZOJ5329】【SDOI2018】战略游戏(圆方树,虚树)

    [BZOJ5329][SDOI2018]战略游戏(圆方树,虚树) 题面 BZOJ 洛谷 Description 省选临近,放飞自我的小Q无心刷题,于是怂恿小C和他一起颓废,玩起了一款战略游戏. 这款战 ...

  10. BZOJ 2286 [Sdoi2011]消耗战(虚树+树形DP)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=2286 [题目大意] 出一棵边权树,每次给出一些关键点,求最小边割集, 使得1点与各个关 ...

随机推荐

  1. [FCC] Cash Register 计算找零

    题目地址: https://chinese.freecodecamp.org/learn/javascript-algorithms-and-data-structures/javascript-al ...

  2. Agileboot 1.6.0 发布啦 - 一款致力于规范/精简/可维护 的Springboot + Vue3的快速开发脚手架

    平台简介 AgileBoot是一套开源的全栈精简快速开发平台,毫无保留给个人及企业免费使用.本项目的目标是做一款精简可靠,代码风格优良,项目规范的小型开发脚手架. 适合个人开发者的小型项目或者公司内部 ...

  3. 甜点cc的2022年回顾总结

    每每到年底,总会感概时间飞逝,总会莫名的心慌几天. 高中时代我就明白了一个道理:自己决定做的事,就算结果再烂以后也不要后悔,因为那无异于否定过去的自己.人不能总是否定自己的过去,因为我觉得这样会打击自 ...

  4. freeswitch的gateway配置方案

    概述 freeswitch是一款简单好用的VOIP开源软交换平台. 在voip的网络模型中,网关是我们经常会遇到的概念. 在freeswitch中,如何配置gateway,如何使用好gateway的模 ...

  5. GitHub上的一个Latex模板

    代码下载:GitHub的项目地址或者在LATEX项目报告模板下载. 编译环境:Latex的编译器,如Ctex软件. 把源码clone或者下载到本地后,根据他的说明 如何开始 使用report.tex开 ...

  6. 利用WordPress搭建属于自己的网站

    怎么用WordPress给自己搭建了一个网站?可能很多人都想拥有属于自己的网站,这篇文章就找你怎么利用WordPress搭建属于自己的网站.如果你也正好有搭建个人网站的想法,那么本文会给你一个参考,我 ...

  7. MVP、原型、概念验证,傻傻分不清楚?

    MVP.原型以及概念验证这三者的概念虽然没有密切的联系,但也有不少人会分不清这三者的区别,在这篇文章中,我们会帮大家区分一下这三个概念.首先是MVP,MVP是Minimum Viable Produc ...

  8. Hive详解(01) - 概念

    Hive详解(01) - 概念 hive简介 Hive:由Facebook开源用于解决海量结构化日志的数据统计工具,是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张表,并提供类S ...

  9. css之transform属性的使用

    1.定义:Transform属性应用于元素的2D或3D转换.这个属性允许你将元素旋转,缩放,移动,倾斜等. 2.常用的属性值: (1)translate(移动):   这个属性值里面含有三个参数,依次 ...

  10. Flutter 3.7 新特性:介绍后台isolate通道

    Flutter 3.7 发布,本人对其中后台 isolate 通道比较感兴趣,迫不及待翻译了下Aaron Clarke文章,第一次翻译,有不足地方欢迎各位大佬们评论区指正,我将持续更新到本文,谢谢. ...