[51nod-1364]最大字典序排列

Online Judge:51nod-1364

Label:线段树,树状数组,二分

题目描述

题解:

根据题意很容易想到60%数据的\(O(N^2logN)\)暴力做法,即每次从大数往小数找,如果它能在m步内换到当前位置就把它换到前面去,然后再把选中的位置设为0,可以用树状数组在\(O(logN)\)完成。

0.O(N^2logN)暴力

cin>>n;
for(int i=1;i<=n;i++){
int x;
cin>>x,pos[x]=i;
}
for(int i=1;i<=n;i++){
for(int j=n;j>=1;j--){
if(已经选择过j)continue;
int times=sum(pos[j])-1;
if(times<=m){
选择j;m-=times;
update(pos[j],-1);
printf("%d\n",j);
break;
}
}
}

对于100%数据考虑优化,思路还是一样的。

1.打离线赛时的做法

上面做法的时间瓶颈在于求\(times<=m\)的最大的数字,由于对于序列来说times递增,发现可以先用二分查找配合树状数组找出一个位置的范围,那么在这个范围内再去查找最大值即可,找最大值可以用线段树实现,然后当你选中了这个最大值后,在树状数组中修改,同时也在线段树中进行单点修改,并维护区间最大值。

这个做法还是比较无脑的,树状数组存某一位前面还剩余的数字个数,线段树存序列中某段区间还留着的数字的最大值,但好打是重点,由于还嵌套了二分查找,时间复杂度为\(O(N*logN*logN)\)。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
struct node{
int l,r,w;
}b[N<<2];
int n,m;
int a[N],id[N],ans[N],c[N];
inline int lowbit(int x){return x&(-x);}
void add(int x,int d){
while(x<=n){
c[x]+=d;
x+=lowbit(x);
}
}
int sum(int x){
int ret=0;
while(x){
ret+=c[x];
x-=lowbit(x);
}
return ret;
}
void build(int l,int r,int o){
b[o].l=l,b[o].r=r;
if(l==r){
b[o].w=a[l];
return;
}
int mid=l+r>>1;
build(l,mid,o<<1);
build(mid+1,r,o<<1|1);
b[o].w=max(b[o<<1].w,b[o<<1|1].w);
}
void update(int o,int pos,int d){
if(b[o].l==pos&&b[o].r==pos){
b[o].w=d;
return;
}
int mid=b[o].l+b[o].r>>1;
if(pos<=mid)update(o<<1,pos,d);
else update(o<<1|1,pos,d);
b[o].w=max(b[o<<1].w,b[o<<1|1].w);
}
int query(int l,int r,int o){
if(b[o].l==l&&b[o].r==r)return b[o].w;
int mid=b[o].l+b[o].r>>1;
if(r<=mid)return query(l,r,o<<1);
else if(l>mid)return query(l,r,o<<1|1);
return max(query(l,mid,o<<1),query(mid+1,r,o<<1|1));
}
int main(){
// freopen("arrange.in","r",stdin);freopen("arrange.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),id[a[i]]=i;
build(1,n,1);
for(int i=1;i<=n;i++)add(i,1);
for(int i=1;i<=n;i++){
int l=1,r=n,lim=l;//二分查找范围
while(l<=r){
int mid=l+r>>1;
if((sum(mid)-1)<=m)l=mid+1,lim=mid;
else r=mid-1;
}
int p=query(1,lim,1);//线段树找
update(1,id[p],0);//线段树单点修改
m-=(sum(id[p])-1);
add(id[p],-1);
ans[i]=p;
}
for(int i=1;i<=n;i++)printf("%d\n",ans[i]);
}

2.赛后一种较为简洁的做法

树状数组中的个数也放在线段树上维护,不要了。

二分查找放在线段树上直接查找,不要了。

所以只要一个线段树就好了,维护两个东西,区间个数s、区间最值mx。时间复杂度为\(O(NlogN)\)。

代码如下☞

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
#define For(a,b,c) for(register int a=b;a<=c;a++)
inline int read(){
int x=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x;
}
int s[N<<2],mx[N<<2],pos[N],id[N],m,n;
void build(int o,int l,int r){
if(l==r){
s[o]=1,mx[o]=id[l];
return;
}
int mid=l+r>>1;
build(o<<1,l,mid),build(o<<1|1,mid+1,r);
mx[o]=max(mx[o<<1],mx[o<<1|1]);
s[o]=s[o<<1]+s[o<<1|1];
}
void update(int o,int l,int r,int pos,int co){
if(l==r){
m-=co,mx[o]=s[o]=0;
return;
}
int mid=l+r>>1;
if(pos<=mid)update(o<<1,l,mid,pos,co);
else update(o<<1|1,mid+1,r,pos,co+s[o<<1]);
mx[o]=max(mx[o<<1],mx[o<<1|1]);
s[o]=s[o<<1]+s[o<<1|1];
}
int query(int o,int l,int r,int d){
if(d<=0)return 0;
if(l==r)return mx[o];
int mid=l+r>>1;
if(d<=s[o<<1])return query(o<<1,l,mid,d);
return max(mx[o<<1],query(o<<1|1,mid+1,r,d-s[o<<1]));
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++)id[i]=read(),pos[id[i]]=i;
build(1,1,n);
for(int i=1;i<=n;i++){
int num=query(1,1,n,min(m+1,s[1]));
printf("%d\n",num);
update(1,1,n,pos[num],0);
}
}

[51nod-1364]最大字典序排列的更多相关文章

  1. 51nod 1364 最大字典序排列(线段树)

    1364 最大字典序排列基准时间限制:1 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 给出一个1至N的排列,允许你做不超过K次操作,每次操作可以将相邻的两个数交换,问能够得到的字 ...

  2. 51Node 1364--- 最大字典序排列(树状数组)

    51Node  1364--- 最大字典序排列(树状数组) 1364 最大字典序排列 基准时间限制:1 秒 空间限制:131072 KB 分值: 80 难度:5级算法题  收藏  关注 给出一个1至N ...

  3. 51nod1364 最大字典序排列

    不断的在cur的后面找最大的符合条件的数扔到cur的前面. 用线段树维护操作就可以了. #include<cstdio> #include<cstring> #include& ...

  4. [Swift-2019力扣杯春季决赛]2. 按字典序排列最小的等效字符串

    给出长度相同的两个字符串:A 和 B,其中 A[i] 和 B[i] 是一组等价字符.举个例子,如果 A = "abc" 且 B = "cde",那么就有 'a' ...

  5. [leetcode](4.21)2. 按字典序排列最小的等效字符串

    给出长度相同的两个字符串:A 和 B,其中 A[i] 和 B[i] 是一组等价字符.举个例子,如果 A = "abc" 且 B = "cde",那么就有 'a' ...

  6. hdu1027(n个数的按字典序排列的第m个序列)

    题目信息:给出n.m,求n个数的按字典序排列的第m个序列 http://acm.hdu.edu.cn/showproblem.php? pid=1027 AC代码: /**  *全排列的个数(次序) ...

  7. poj 1146 ID Codes (字符串处理 生成排列组合 生成当前串的下一个字典序排列 【*模板】 )

    ID Codes Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 6229   Accepted: 3737 Descript ...

  8. 51nod 1020 逆序排列

    http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1020 题意: 思路: 一开始用了三重循环... 设f(n,k)表示n个数 ...

  9. 46. 47. Permutations and Permutations II 都适用(Java,字典序 + 非字典序排列)

    解析: 一:非字典序(回溯法) 1)将第一个元素依次与所有元素进行交换: 2)交换后,可看作两部分:第一个元素及其后面的元素: 3)后面的元素又可以看作一个待排列的数组,递归,当剩余的部分只剩一个元素 ...

随机推荐

  1. Google Projectsheet Planning 插件的WBS

    生成 WBS的序列號 在 Sldebar中的 "WBS" 按鈕: "< WBS" 取消下級目錄 "WBS >" 生成下級目錄 G ...

  2. 导出Excel格式数据

    /** * 导出规则统计数据 excel格式 * @param bill * @return */ public String exportExamsignupExamnoExcel ( String ...

  3. linux watch命令查看网卡流量

    watch命令可以反复的执行一个命令,默认时间间隔为2秒钟.TX是发送(transport),RX是接收(receive)RX bytes:总下行流量TX bytes:总上行流量 可以每隔两秒监视网络 ...

  4. VS2010-MFC(对话框:消息对话框)

    转自:http://www.jizhuomi.com/software/171.html 前面几节讲了属性页对话框,我们可以根据所讲内容方便的建立自己的属性页对话框.本节讲解Windows系统中最常用 ...

  5. day 83 Vue学习之五DIY脚手架、webpack使用、vue-cli的使用、element-ui

      Vue学习之五DIY脚手架.webpack使用.vue-cli的使用.element-ui   本节目录 一 vue获取原生DOM的方式 二 DIY脚手架 三 vue-cli脚手架的使用 四 we ...

  6. SQL 分组后获取每组中最大值

    场景:sql server 2008 drop table ID CREATE TABLE ID ( id ,) not null, code int , D date, PRIMARY KEY (i ...

  7. CentOS升级GDB

    需要先安装ncurses-devel和texinfo 如果不安装ncurses-devel,编译会出错 如果不安装texinfo或者版本太低,安装会出错 1.yum install -y ncurse ...

  8. 20.multi_协程方法抓取总阅读量

    # 用asyncio和aiohttp抓取博客的总阅读量 (提示:先用接又找到每篇文章的链接) # https://www.jianshu.com/u/130f76596b02 import re im ...

  9. 18.scrapy_maitian_analysis

    1_info.py # encoding: utf-8 import pandas as pd # 租房 基本信息 # 读取文件 df=dataframe df = pd.read_json(&quo ...

  10. loj2513 治疗之雨

    题意:你的英雄一开始血量为p,你还有m个队友,血量无穷.血量上限为n,下限为0.如果血量满了就不能加血.每次启动操作,随机给m+1个英雄加1点血,然后等概率随机k次每次对于英雄扣1点血.求期望操作几次 ...