简要题意:给定一个长为 \(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. Java开发学习(四十八)----MyBatisPlus删除语句之逻辑删除

    1.逻辑删除 接下来要讲解是删除中比较重要的一个操作,逻辑删除,先来分析下问题: 这是一个员工和其所签的合同表,关系是一个员工可以签多个合同,是一个一(员工)对多(合同)的表 员工ID为1的张业绩,总 ...

  2. 日爬百万数据的域名限制、url的清洗和管理

    一.域名去重1.检测开头:link.startswith('http') txt = "Hello, welcome to my world." x = txt.startswit ...

  3. C# 11新特性之file关键字

    C#11 添加了文件作用域类型功能:一个新的 file 修饰符,可以应用于任何类型定义以限制其只能在当前文件中使用.这样,我们可以在一个项目中拥有多个同名的类. 目录 示例 file不可以与其他修饰符 ...

  4. 记一次 .NET 某安全生产信息系统 CPU爆高分析

    一:背景 1.讲故事 今天是的第四天,头终于不巨疼了,写文章已经没什么问题,赶紧爬起来写. 这个月初有位朋友找到我,说他的程序出现了CPU爆高,让我帮忙看下怎么回事,简单分析了下有两点比较有意思. 这 ...

  5. 基于Hexo搭建静态博客

    关于静态博客 通常来讲,建立个人博客有2种方式: 第一,直接在第三方博客平台注册博客空间,如:博客园,简书,CSDN等,这种方式建立的博客,所有数据都存放在博客平台. 第二,自建博客系统,这种方式就是 ...

  6. WCH以太网相关芯片资料总结

    网络产品线产品分类 1.接口控制类.CH395Q           http://www.wch.cn/search?t=all&q=395CH395LCH392F            h ...

  7. WCF 服务容器化的一些问题

    背景 目前项目当中存有 .NET Framework 和 .NET Core 两种类型的项目,但是都需要进行容器化将其分别部署在 Windows 集群和 Linux 集群当中.在 WCF 进行容器化的 ...

  8. [生命科学] 生物基础实验之DNA提取

    生物基础实验之DNA提取实验 基因组DNA的提取通常用于构建基因组文库.Southern杂交(包括RFLP)及PCR分离基因等.利用基因组DNA较长的特性,可以将其与细胞器或质粒等小分子DNA分离. ...

  9. HBase详解(03) - HBase架构和数据读写流程

    RegionServer 架构 每个RegionServer可以服务于多个Region 每个RegionServer中有多个Store, 1个WAL和1个BlockCache 每个Store对应一个列 ...

  10. Java基础学习笔记-运算符ヽ( ̄▽ ̄)و

    运算符 运算符-按功能分为7种 1.赋值运算符 广义赋值运算符 +=, -=, *=, /=, %=, &=, |= 一般形式:变量名 运算符=表达式 两侧的类型不一致会进行 自动类型转换 和 ...