考虑到本文读者年龄原因,本文改为使用简体中文撰写。

题目描述

今有正整数 n,kn,kn,k,求 1−n1-n1−n 共 nnn 个数的全排列,按字典序的第 kkk 个。

数据满足 1≤n≤105,1≤k≤min⁡(n!,1020 000).1\leq n\leq10^5,1\leq k\leq\min(n!,10^{20\ 000}).1≤n≤105,1≤k≤min(n!,1020 000).

Solution\text{Solution}Solution

请注意 kkk 的上限! ls忘了打。这很重要!

对于 50%50\%50% 及更低的数据,ls已经解释得很详尽,在此无需赘述。

尝试使用 DeCantor Expansion(逆康托展开)算法求解。这篇文章有详细解释,看不懂找 ls 他英语一级棒。时间复杂度 O(n2)O(n^2)O(n2);

预处理 1−n1-n1−n 的阶乘,时间复杂度 O(n2)O(n^2)O(n2);

朴素的高精度除法,时间复杂度 O(n2)O(n^2)O(n2);

综上所述,总的时间复杂度为 O(n2)O(n^2)O(n2)。贴上代码。

#include<cstdio>
#include<cstdlib>
#include<cstring> #define reg register
typedef long long ll; const int MAXN=1010; //最长 1000位
const int BYTE=8; //压8位,也就是说一个int代表8位数
const int MB=MAXN/BYTE+1; //新的长度 int pw[BYTE+10]; //pw[i]表示10^i
char str[MAXN+10]; struct Bignum{ //高精度类
int a[MB+10];
Bignum(){
memset(a,0,sizeof(a));
}
void read(){ //读入、赋值、压位
scanf("%s",str+1);
int len=strlen(str+1);
for(reg int i=1;i<=len;++i)
a[i]=str[len-i+1]-'0';
for(reg int i=1;i<=len/BYTE+1;++i){
int cnt=0;
for(reg int j=i*BYTE;j>=(i-1)*BYTE+1;--j)
cnt=cnt*10+a[j];
a[i]=cnt;
}
//注意清零
for(reg int i=len/BYTE+2;i<=len;++i) a[i]=0;
}
void print(){ //打印一个数
bool tf=0;
for(reg int i=MB+1;i>=1;--i){
if(a[i]&&!tf){
tf=1;
printf("%d",a[i]);
continue;
}
if(tf) printf("%08d",a[i]);
}
if(!tf) putchar('0');
}
//重定义Bignum类的运算,高精+高精
friend Bignum operator+(const Bignum a,const Bignum b){
Bignum c;
for(reg int i=1;i<=MB+1;++i)
c.a[i]=a.a[i]+b.a[i];
for(reg int i=1;i<=MB+1;++i)
if(c.a[i]>=pw[BYTE]){
c.a[i+1]+=c.a[i]/pw[BYTE];
c.a[i]%=pw[BYTE];
}
return c;
}
//高精减
friend Bignum operator-(const Bignum a,const Bignum b){
Bignum c;
for(reg int i=1;i<=MB+1;++i)
c.a[i]=a.a[i]-b.a[i];
for(reg int i=1;i<=MB+1;++i)
if(c.a[i]<0){
c.a[i]+=10;
--c.a[i+1];
}
return c;
}
//高精乘低精
friend Bignum operator*(const Bignum a,const int b){
Bignum c;
ll d[MB+10];
for(reg int i=1;i<=MB+1;++i)
d[i]=(ll)a.a[i]*b;
for(reg int i=1;i<=MB+1;++i){
if(d[i]>=pw[BYTE]){
d[i+1]+=d[i]/pw[BYTE];
d[i]%=pw[BYTE];
}
c.a[i]=(int)d[i];
}
return c;
}
//注意此处的>等价于>=
friend bool operator>(const Bignum a,const Bignum b){
for(reg int i=MB+1;i>=1;--i)
if(a.a[i]!=b.a[i])
return a.a[i]>b.a[i];
return 1;
}
}ft[MB+10],k,one;
//ft[i]表示i的阶乘
struct Pair{ //两个大数
Bignum a,b;
};
int n;
bool tf[MAXN+10]; //tf[i]表示第i个数是否可以被取用
int ans[MAXN+10]; //ans是答案数组 //朴素高精除,返回的a是商,b是余数
Pair operator/(const Bignum a,const Bignum b){
Pair c;Bignum d;
for(reg int i=MB+1;i>=1;--i){
d=d*10;d.a[1]=a.a[i];
c.a=c.a*10;
while(d>b){
d=d-b;
++c.a.a[1];
}
}
c.b=d;
return c;
}
void reset(){ //初始化
pw[0]=1;
for(reg int i=1;i<=BYTE;++i)
pw[i]=pw[i-1]*10;
ft[1].a[1]=1;
for(reg int i=2;i<=n;++i)
ft[i]=ft[i-1]*i;
memset(tf,1,sizeof(tf));
one.a[1]=1;
k=k-one;
}
int cg(const Bignum a){ //将一个Bignum转换成int
int cnt=0;
for(reg int i=MB+1;i>=1;--i)
cnt=cnt*pw[BYTE]+a.a[i];
return cnt;
}
int find(int x){ //找到能使用的第x小的数
int cnt=0;
for(reg int i=1;i<=n;++i)
if(tf[i]){
++cnt;
if(cnt==x){
tf[i]=0; //取用它
return i;
}
}
return -1;
}
void work(){ //DeCantor Expansion
Pair p;
int cnt;
//此处参考算法解释的文章
for(reg int i=1;i<n;++i){
p=k/ft[n-i];k=p.b;
cnt=cg(p.a);
ans[i]=find(cnt+1);
}
ans[n]=find(1); //最后只剩一个数了,取用它
}
int main(){
scanf("%d",&n);k.read();
reset();
work();
for(reg int i=1;i<=n;++i)
printf("%d ",ans[i]);
}

最后一个点的 100 000100\ 000100 000,看上去很吓人,但是由于 k≤1020 000k\leq10^{20\ 000}k≤1020 000,所以前 96 00096\ 00096 000 位左右都是原来的顺序(本质上还是 n2n^2n2 的)。这个点的特判我不管了你自己写。

优化方法

  1. 使用树状数组维护一个数是否被取用。时间复杂度 O(n log⁡ n)O(n\ \log\ n)O(n log n);
  2. 快速多项式除法,或者二分答案。时间复杂度 O(n log⁡ n)O(n\ \log\ n)O(n log n);
  3. 使用压位存储。若压 BYTEBYTEBYTE 位,则时间复杂度 O(O(原来)BYTE2)O(\frac{O(原来)}{BYTE^2})O(BYTE2O(原来)​);
  4. 使用任意模数快速阶乘。时间复杂度 O(n log⁡ n)O(n\ \log\ n)O(n log n)(未实践)。

期望时间复杂度 O(n log⁡ n)O(n\ \log\ n)O(n log n)。若哪位巨佬实现了,请评论并收下我的膝盖orz。

Did You AK Today? (今天你AK了吗?)的更多相关文章

  1. 德州扑克AK打法攻略

    AK是所有德扑网游中最受争议的底牌,也是一副令人又爱又恨的底牌.<德州扑克培训大师>根据国内德州扑克网游特性,为大家制作了第一套AK打法攻略,希望所有玩家从今天开始能正确认识AK,发挥AK ...

  2. 业务网关之AK中心建设

    啥是AK AK(Access Key)是一种身份证明,它解决了"资源的使用者是谁"这个问题,比如在生活中,身份证可以证明你是你,而在云计算或程序中,AK能证明你是这个应用的拥有者. ...

  3. 从云AK泄露利用看企业特权管理

    从云AK泄露利用看企业特权管理 目录 - 缘起 - 当前主流AK泄露检测方式 - 防止AK滥用的关键要素? - 哪些算特权账号管理? - 如何做特权账号管理? - 特权管理与堡垒机.IAM.零信任的关 ...

  4. //给定N个整数序列{A1,A2,A3...An},求函数f(i,j)=(k=i~j)Ak的求和

    //给定N个整数序列{A1,A2,A3...An},求函数f(i,j)=(k=i~j)Ak的求和 # include<stdio.h> void main() { ,sum1; ]={,- ...

  5. 百度地图秘钥ak的获取

    今天打开网站的时候出现了这个问题“百度未授权使用地图API, 可能是因为您提供的密钥不是有效的百度开放平台密钥或此密钥未对本应用的百度地图JavasoriptAPI授权.....”经过研究终于知道什么 ...

  6. 百度地图LBS开放平台AK一直没有用

    http://api.map.baidu.com/geoconv/v1/?coords=114.21892734521,29.575429778924;114.21892734521,29.57542 ...

  7. 移动平台作业——天气预报——天气数据的获得——为应用申请百度ak码

    需求: 可切换城市 可实时更新(按钮或手势或下拉刷新) 可现实未来三日的天气 不限制横屏或者竖屏,不限制布局样式,但要求得到的数据均需显示(北京.天气数据.天气图标) 提示: 获得实时天气数据(任选一 ...

  8. 点分治练习:不虚就是要AK

    [题面] 不虚就是要AK(czyak.c/.cpp/.pas) 2s 128M czy很火.因为又有人说他虚了.为了证明他不虚,他决定要在这次比赛AK. 现在他正在和别人玩一个游戏:在一棵树上随机取两 ...

  9. 设正整数n的十进制表示为n=ak……a1a0(0<=ai<=9,0<=i<=k,ak!=0),n的个位为起始数字的数字的正负交错之和T(n)=a0+a1+……+(-1)kak,证明:11|n的充分必要条件是11|T(n);(整除理论1.1.2))

    设正整数n的十进制表示为n=ak……a1a0(0<=ai<=9,0<=i<=k,ak!=0),n的个位为起始数字的数字的正负交错之和T(n)=a0+a1+……+(-1)kak, ...

  10. 有两个序列A和B,A=(a1,a2,...,ak),B=(b1,b2,...,bk),A和B都按升序排列。对于1<=i,j<=k,求k个最小的(ai+bj)。要求算法尽量高效。

    有两个序列A和B,A=(a1,a2,...,ak),B=(b1,b2,...,bk),A和B都按升序排列.对于1<=i,j<=k,求k个最小的(ai+bj).要求算法尽量高效. int * ...

随机推荐

  1. VS中一些提高编码效率的快捷键

    场景 新手入门,看见大神们在VS中只用键盘就可以完成一系列操作. 整理一些常用的能提高效率的快捷键. 关注公众号霸道的程序猿获取编程相关电子书.教程推送与免费下载. 大量编程视频教程:https:// ...

  2. 转换地图 (康托展开+预处理+BFS)

    Problem Description 在小白成功的通过了第一轮面试后,他来到了第二轮面试.面试的题目有点难度了,为了考核你的思维能量,面试官给你一副(2x4)的初态地图,然后在给你一副(2x4)的终 ...

  3. Linux 笔记 - 第五章 Linux 用户与用户组管理

    博客地址:http://www.moonxy.com Linux 是一个多用户的操作系统,在日常的使用中,从安全角度考虑,应该尽量避免直接使用 root 用户登录,而使用普通用户. 1. 关于用户 u ...

  4. [Leetcode] 第307题 区域和检索-数组可修改

    参考博客:(LeetCode 307) Range Sum Query - Mutable(Segment Tree) 一.题目描述 给定一个整数数组  nums,求出数组从索引 i 到 j  (i  ...

  5. 将Jexus+mono和网站一起通过Dockerfile打包到docker镜像

    上次使用别人打包好的docker镜像,往里边加入文件,最终asp.net的docker容器化运行. 这次决定直接全新打包一个jexus+asp.net网站的docker包. 进入root目录,并在ro ...

  6. Android Studio [水平布局LinearLayout]

    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=&quo ...

  7. JAVA线程基础概念及使用

    一.线程和进程的区别 在操作系统中所有运行的任务通常对应一个进程,进程是系统进行资源分配和调度的一个独立单位.线程是进程的组成部分,一个进程最少包含一个线程.并发和并行的区别是,并发指的在同一时刻内, ...

  8. JS/jQuery点击某元素之外触发事件

    JQuery // 第一步:点击任何地方都触发事件 $(document).click(function(){ alert("点击当前页面的任何地方都触发此点击事件:"); }); ...

  9. 品Spring:对@PostConstruct和@PreDestroy注解的处理方法

    在bean的实例化过程中,也会用到一系列的相关注解. 如@PostConstruct和@PreDestroy用来标记初始化和销毁方法. 平常更多的是侧重于应用,很少会有人去了解它背后发生的事情. 今天 ...

  10. python 写入txt的新方法

    最新发现有新方法可以对txt等进行操作,比较有意思,之前没见过,故记录下 传统方法 with open(ur'D:\Desktop\a123.txt', 'a') as f: #以写的方式打开 f.w ...