当前位置: 首页 > news >正文

【算法速成课 3】康托展开(Cantor Expansion)/ 题解 P3014 [USACO11FEB] Cow Line S

专栏:算法速成课_proMatheus的博客-CSDN博客

前置知识:树状数组


前导

康托展开(Cantor Expansion)是一种将一个排列,映射为一个唯一整数的编码方法。

常用于排列的哈希、状态压缩或字典序编号等场景。

题意

任务一:求一个全排列是第几个全排列,按字典序(即从小到大)。

任务二:求第 K 个全排列。

1.康托展开(任务 1)

分析

假如我问你,求 13425 是第几个 5 的全排列,你会怎么做?

先一个个列出来?

12345,12354,12435,12453,12534,12543,13245,13254,13425...

发现是第 9 个。

那有没有更快的方法?

我们发现 13425 的第二个位是 3,这代表 12XXX 都在它的前面。

直接加上所有 12XXX 的数量 6

接着发现第三个位是 4,照理来说应该是它后面、比它小的 2 在它这个位置,

但现在这里是 4,代表 132XX 都在 13425 的前面,加上数量 2

6+2=8,这是所有在 13425 前面的数,即 13425 是第 9 个全排列。

再回顾总结下,假设要求 n 的全排列。

如果有比当前第 i 个位上的数 x 小,且还没出现过的数 a

那么这个 a 肯定能顶替 x 得到更小的字典序,排在题目给出排列的前面(1 到 i-1 位不动)。

累计答案加上 (n-i)!,这表示所有 a 顶替 x 构成的排列都排在题目给出排列的前面

很明显,有几个这样的 a 就应该加几个 (n-i)!

实现

例题:P5367 【模板】康托展开 - 洛谷

想要求出比当前 x 小且还没出现过数的个数,可以考虑使用树状数组 / 线段树 / 平衡树。

后两个都稍有麻烦,我们用树状数组

时间复杂度 O(nlog\ n)

代码:

#include<bits/stdc++.h>
using namespace std;typedef long long LL;
const int N = 1e6 + 10;
const LL P = 998244353;int n, a[N];
LL c[N], fac[N];void add(int x, LL d) {for (; x <= n; x += x & -x) {c[x] = (c[x] + d) % P;}
}LL get_sum(int x) {LL res = 0;for (; x >= 1; x -= x & -x) {res = (res + c[x]) % P;}return res;
}int main () {ios::sync_with_stdio(false);cin.tie(0);cin >> n;for (int i = 1; i <= n; i ++) {cin >> a[i];}memset(c, 0, sizeof(c));fac[0] = 1;for (int i = 1; i <= n; i ++) {  // 初始化阶乘数组 fac[i] = fac[i - 1] * i % P;   // 一定要取模!! }LL ans = 0;for (int i = 1; i <= n; i ++) {int x = a[i];LL t = ( (x - 1) - get_sum(x - 1) + P) % P;   // get_sum 求出来的是出现过比 x 小的数,要求没出现过的 ans = (ans + t * fac[n - i] % P) % P;add(x, 1);   // 把 x 放进去 }cout << (ans + 1) << "\n";   // 别忘了加 1,ans 是在题目给出排列前面的排列数 return 0;
}

2.逆康托展开(任务 2)

分析

聪明的你肯定想到了应该怎么做:

假设给出的排列序号是 K,循环 1 到 nK 先减减。

这样 K 就代表着在答案排列前面的排列个数,接下来每一个位都根据前面的排列定值

当前循环到 i

如果 K 里面有 a 个 (n-i)!,取 1 到 i-1 位未出现的第 a+1 小数为 t

那么当前位就等于 t

因为按理说当前位就该是 1 到 i-1 位未出现的最小数了,但 K 里又有 a 个 (n-i)!

这代表在第 i 位,还有 a 个比它小的数构成的排列排在它前面(1 到 i-1 位不动),

又由于不能重复,所以取 1 到 i-1 位未出现的第 a+1 小数。

别忘了 K-=a*(n-i)!

还是拿 13425 的例子,现在我们只知道 K=8\ \ (9-1)

当 i=1 时,K 里面有 0 个 (n-1)!=241 到 0 位未出现的第 1 小数为 1

当 i=2 时,K 里面有 1 个 (n-2)!=61 到 1 位未出现的第 2 小数为 3

K=2

当 i=3 时,K 里面有 1 个 (n-3)!=21 到 2 位未出现的第 2 小数为 4

K=0

当 i=4 时,K 里面有 0 个 (n-4)!=11 到 3 位未出现的第 1 小数为 2

当 i=5 时,K 里面有 0 个 (n-5)!=11 到 4 位未出现的第 1 小数为 5

得出 13425

实现

难点在求 1 到 i-1 位未出现的最小数,这玩意 O(n^2),想优化上线段树 or 树状数组上倍增。

(不过例题 n 很小不需要,无所谓我会给出两份代码)

例题:P3014 [USACO11FEB] Cow Line S - 洛谷

P 操作就是逆展开。

20!=2,432,902,008,176,640,000,long long 的范围是 -2^{63} 到 2^{63}-1

即差不多 \pm 9.2e18,可以放心用。

逆展开 O(n^2) 代码(我用了 set,总时间复杂度 O(Kn^2) 可 AC):

#include<bits/stdc++.h>
using namespace std;typedef long long LL;
const int N = 22;int n, a[N];
LL c[N], fac[N];void add(int x, LL d) {for (; x <= n; x += x & -x) {c[x] += d;}
}LL get_sum(int x) {LL res = 0;for (; x >= 1; x -= x & -x) {res += c[x];}return res;
}int main () {ios::sync_with_stdio(false);cin.tie(0);int K;cin >> n >> K;fac[0] = 1;for (int i = 1; i <= n; i ++) {fac[i] = fac[i - 1] * i;   // 阶乘这一块 /. }while (K --) {char s[5];cin >> s;if (s[0] == 'Q') {   // 正展开 memset(c, 0, sizeof(c));   // 每个询问都要清空一次 LL ans = 0;for (int i = 1; i <= n; i ++) {cin >> a[i];LL t = a[i] - 1 - get_sum(a[i] - 1);ans += t * fac[n - i]; add(a[i], 1);}cout << (ans + 1) << "\n";}else {   		   // 逆展开 LL k;     // 这玩意可有 20! 那么大 cin >> k; k --;   // 减减 set<int> set_;  // 未使用数字集合 for (int i = 1; i <= n; i ++) {set_.insert(i);    // 全都放进去 }for (int i = 1; i <= n; i ++) {int aa = k / fac[n - i];  // 重名了用 aaauto it = set_.begin(); advance(it, aa);   // 移到第 aa + 1 个元素 a[i] = *it;set_.erase(*it);     // 删了 k -= aa * fac[n - i];  // 别忘了减 }for (int i = 1; i <= n; i ++) {cout << a[i] << " ";}cout << "\n";}}return 0;
} 

逆展开树状数组倍增 O(n\ logn) 做法,总时间复杂度 O(Kn\ logn)

#include<bits/stdc++.h>
using namespace std;typedef long long LL;
const int N = 22;int n, a[N];
LL c[N], fac[N];void add(int x, LL d) {for (; x <= n; x += x & -x) {c[x] += d;}
}LL get_sum(int x) {LL res = 0;for (; x >= 1; x -= x & -x) {res += c[x];}return res;
}// 在树状数组 c 中找第 k 小的可用数(k 从 1 开始)
// 这里利用了树状数组的特性,即查询 1 到 n 最多遍历 log n 个值 
int kth(int k) {int p = 0, s = 0;    // p 是当前树状数组上刚刚遍历到的节点(当前遍历范围可使用数字最大值) // s 是当前已遍历范围里可使用数字的总个数 // 整个过程就是不断调整 p 的大小(遍历范围的最大值)// 来看看 s 什么时候刚好等于 k - 1// 由于最后 s 肯定停在 k - 1 的临界值(再大一点就不是了)// 所以 p + 1 是第 k 个数 // n <= 20,所以 1 << 5 = 32 足够for (int i = 5; i >= 0; i --) {int t = p + (1 << i);if (t <= n && s + c[t] < k) {  // 节点还小于 n,总个数要小于 k s += c[t];p = t;}}// 现在 p 是最大的满足 get_sum(p) < k 的下标 return p + 1;
}int main () {ios::sync_with_stdio(false);cin.tie(0);int K;cin >> n >> K;fac[0] = 1;for (int i = 1; i <= n; i ++) {fac[i] = fac[i - 1] * i;   // 阶乘这一块 /. }while (K --) {char s[5];cin >> s;if (s[0] == 'Q') {   // 正展开 memset(c, 0, sizeof(c));   // 每个询问都要清空一次 LL ans = 0;for (int i = 1; i <= n; i ++) {cin >> a[i];LL t = a[i] - 1 - get_sum(a[i] - 1);ans += t * fac[n - i]; add(a[i], 1);}cout << (ans + 1) << "\n";}else {   		   // 逆展开 LL k;     // 这玩意可有 20! 那么大 cin >> k; k --;   // 减减 memset(c, 0, sizeof(c));   // 现在这个树状数组是存未使用的数字 for (int i = 1; i <= n; i++) {add(i, 1);     // 全都放进去 }for (int i = 1; i <= n; i ++) {int aa = k / fac[n - i];   // 重名了用 aaint t = kth(aa + 1);     // 第 (aa + 1) 小,kth 是 1-indexeda[i] = t;add(t, -1);              // 删掉这个数k -= aa * fac[n - i];      // 别忘了减 }for (int i = 1; i <= n; i ++) {cout << a[i] << " ";}cout << "\n";}}return 0;
}

http://www.dtcms.com/a/531912.html

相关文章:

  • 【java面向对象进阶】------抽象类+接口 综合实例
  • 自然语言处理实战——英法机器翻译
  • 图数据库系统学习指南(从入门到进阶)
  • 信息服务平台网站西峰住房和城乡建设局网站
  • 影像生成评估指标FID
  • 【金仓数据库】ksql 指南(三) —— 创建与管理表空间和模式
  • 高并发内存池 - 开发记录07
  • 品牌网站建设平台杭州排名优化公司
  • 做网站用的笔记本配置检测asp网站死循环
  • 建材公司网站建设方案金融网站建设方案ppt
  • 零基础从头教学Linux(Day 56)
  • 世冠科技2025复杂装备数智化研发与运维技术研讨会暨 GCKontrol GCAir 10.0 版本产品发布会圆满落幕
  • Qt6 学习——一个Qt桌面应用程序
  • 【Linux】传输层协议TCP
  • 前端监控:错误捕获与行为日志全解析
  • 第一部分:网络基础
  • Socket详解
  • Ceph存储
  • [人工智能-大模型-87]:模型层技术 - “神经网络架构演进的全景地图”,“从简单到复杂、从单一到智能” - 通俗易懂版。
  • windows 2003 取消网站访问密码wordpress黑镜百度云盘
  • Spring Boot3零基础教程,自定义 starter,把项目封装成依赖给别人使用,笔记65
  • 建设足球网站的心得和意义渠道分销管理系统
  • 【PLC】汇川InoTouchPad在Win11上显示太小
  • OpenHarmony蓝牙技术全解析:从设备发现到数据传输的完整流程
  • 解压版MySQL的安装与卸载
  • C++编程基础(五):字符数组和字符串
  • 在线旅游网站平台有哪些山东泰安房价2023最新价格
  • [3D Max 基础知识分享]—多孔结构模型编辑
  • 【C++篇】C++11入门:踏入C++新世界的大门
  • 爬虫数据清洗可视化案例之全球灾害数据