简要题意:给定一个长为 \(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. 《HTTP权威指南》– 5.Web服务器

    Web服务器概念: 实现了HTTP和相关的TCP连接处理,负责管理Web服务器提供的资源,以及对Web服务器的配置.控制及扩展方面的管理. 各种不同的形式: 通过软件Web服务器:运行在标准的.有网络 ...

  2. Django测试脚本-单表操作(增删改查)-必知必会13条-神奇的双下划线

    目录 一:Django测试脚本 1.测试环境准备 2.tests.py 3.models.py 4.切换MySQL数据库 二:单表操作 1.pk关键字与get关键字 2.增 3.删 4.修 三:必知必 ...

  3. vue-router路由之路-极简教程

    01.什么是前端路由? 前端路由的一个大背景就是当下流行的单页应用SPA,一些主流的前端框架,如vue.react.angular都属于SPA,那什么是SPA呢? 1.1.SPA SPA(single ...

  4. TypeError: __str__ returned non-string (type WebStepInfo)

    错误代码: class CaseStep(models.Model): id = models.AutoField(primary_key=True) casetep = models.Foreign ...

  5. Jmeter 之 If 逻辑控制器

    在Jmeter 中如要在某种场景中才执行特殊请求,此时可用If 逻辑控制器来实现. If 逻辑控制器顾名思义当符合某个条件时则执行,添加路径:测试计划->线程组->逻辑控制器->if ...

  6. ob_aes_饭团影视

    网站 'aHR0cHM6Ly93d3cuZmFudHVhbmhkLmNvbS9wbGF5L2lkLTExNzctMS0xLmh0bWw=' 搜索mp4,找这个链接从哪来的  打开之后会有明显奇怪的参数 ...

  7. JavaScript:变量:声明和赋值变量时,内存结构是什么样的?

    这里只是大概画出内存结构的模型图,方便理解当我们声明变量和赋值变量时,到底在干嘛. 如上图所示,a赋值一个对象{},b赋值字符串hello: 于是内存里划了三个区域给我们,一个存储我们声明的变量表,即 ...

  8. 【博学谷学习记录】超强总结,用心分享|前端开发HTML知识总结

    HTML知识总结 字体加粗标签 注释快捷键 Ctrl + / 代码后缩进:shift + tad 1HTML标签 1.1排版标签介绍 1.1.1标题标签 <h1>1级标题<h1/&g ...

  9. HelloGitHub 最受欢迎的开源项目 Top10(2022年)

    再见 2022,你好 2023! HelloGitHub 也随着 2023 年的到来,更新到了第 81 期 开始迈向第 7 个年头啦. 在过去的 2022 年,我们一共发布了 12 期月刊.分享了 5 ...

  10. vue3实现一个抽奖小项目

    前言 在公司年会期间我做了个抽奖小项目,我把它分享出来,有用得着的可以看下. 浏览链接:http://xisite.top/original/luck-draw/index.html 项目链接:htt ...