Permutation UVA - 11525

康托展开

题目给出的式子(n=s[1]*(k-1)!+s[2]*(k-2)!+...+s[k]*0!)非常像逆康托展开(将n个数的所有排列按字典序排序,并将所有排列编号(从0开始),给出排列的编号得到对应排列)用到的式子。可以想到用逆康托展开的方法。但是需要一些变化:

for(i=n;i>=;i--)
{
s[i-]+=s[i]/(n-i+);
s[i]%=(n-i+);
}

例如:n=3时,3=0*2!+0*1!+3*0!应该变为3=1*2!+1*1!+0*0!。就是“能放到前面的尽量放到前面”。

然后,生成这个排列的方法就是:首先有一个集合,把1~n的所有数放进集合。然后从小到大枚举答案的位置i,对于每个i,取出当前集合中第(s[i]+1)大的元素输出,并从集合中去掉这个元素。

这么做的原因是:对于某个位置i,取当前集合中第x大的元素,那么就“跳过”了(x-1)!个元素。

因此可以用任意一种平衡树水过去

 #include<cstdio>
#include<cstdlib>
#include<ctime>
#include<algorithm>
using namespace std;
#define MAXI 2147483647
//http://blog.csdn.net/h348592532/article/details/52837228随机数
int rand1()
{
static int x=;
return x=(48271LL*x+)%;
}
struct Node
{
Node* ch[];
int r;//优先级
int v;//value
int size;//维护子树的节点个数
int num;//当前数字出现次数
int cmp(int x) const//要在当前节点的哪个子树去查找,0左1右
{
if(x==v) return -;
return v<x;//x<v?0:1
}
void upd()
{
size=num;
if(ch[]!=NULL) size+=ch[]->size;
if(ch[]!=NULL) size+=ch[]->size;
}
}nodes[];
int mem,n;
Node* root=NULL;
void rotate(Node* &o,int d)
{
Node* t=o->ch[d^];o->ch[d^]=t->ch[d];t->ch[d]=o;
o->upd();t->upd();//o是t子节点,一定要这个顺序upd
o=t;//将当前节点变成旋转完后新的父节点
}
Node* getnode()
{
return &nodes[mem++];
}
void insert(Node* &o,int x)
{
if(o==NULL)
{
o=getnode();o->ch[]=o->ch[]=NULL;
o->v=x;o->r=rand1();o->num=;
}
else
{
if(o->v==x) ++(o->num);
else
{
int d=o->v < x;//x < o->v?0:1
insert(o->ch[d],x);
if(o->r < o->ch[d]->r) rotate(o,d^);//不是 x < o->ch[d]->r
}
}
o->upd();
}
void remove(Node* &o,int x)
{
int d=o->cmp(x);
if(d==-)
{
if(o->num > )
{
--(o->num);
}
if(o->num == )
{
if(o->ch[]==NULL) o=o->ch[];
else if(o->ch[]==NULL) o=o->ch[];
else
{
//int d2= o->ch[0]->r > o->ch[1]->r;//o->ch[0]->r > o->ch[1]->r ? 1:0
int d2=o->ch[]->r < o->ch[]->r;//o->ch[1]->r <= o->ch[0]->r
rotate(o,d2);
remove(o->ch[d2],x);
//左旋则原节点变为新节点的左子节点,右旋相反
}
}
}
else remove(o->ch[d],x);
if(o!=NULL) o->upd();
}
bool find(Node* o,int x)
{
int d;
while(o!=NULL)
{
d=o->cmp(x);
if(d==-) return ;
else o=o->ch[d];
}
return ;
}
int kth(Node* o,int k)
{
if(o==NULL||k<=||k > o->size) return ;
int s= o->ch[]==NULL ? : o->ch[]->size;
if(k>s&&k<=s+ o->num) return o->v;
else if(k<=s) return kth(o->ch[],k);
else return kth(o->ch[],k-s- o->num);
}
int rk(Node* o,int x)
{
int r=o->ch[]==NULL ? : o->ch[]->size;
if(x==o->v) return r+;
else if(x<o->v) return rk(o->ch[],x);
else return r+ o->num +rk(o->ch[],x);
}
int pre(Node* o,int x)
{
if(o==NULL) return -MAXI;
int d=o->cmp(x);
if(d<=) return pre(o->ch[],x);
else return max(o->v,pre(o->ch[],x));
}
int nxt(Node* o,int x)
{
if(o==NULL) return MAXI;
int d=o->cmp(x);
if(d!=) return nxt(o->ch[],x);
else return min(o->v,nxt(o->ch[],x));
}
int xx[];
int T;
int main()
{
int i;
scanf("%d",&T);
while(T--)
{
root=NULL;mem=;
scanf("%d",&n);
for(i=;i<=n;i++) insert(root,i);
for(i=;i<=n;i++) scanf("%d",&xx[i]);
for(i=n;i>=;i--)
{
xx[i-]+=xx[i]/(n-i+);
xx[i]%=(n-i+);
}
for(i=;i<n;i++)
{
printf("%d ",kth(root,xx[i]+));
remove(root,kth(root,xx[i]+));
}
printf("%d\n",kth(root,xx[n]+));//这题卡格式
remove(root,kth(root,xx[n]+));
}
return ;
}

同样可以用树状数组做。树状数组中存某个值出现的次数。也就是说,开始的集合中,如果数字x出现了y次,就在树状数组的位置x处加y。

第k大数,就是有至少k个数小于等于它的最小数。

那么,如果要求第k大数,就二分第k大数的值p,显然可以在log的时间内求出小于等于p的数的个数q,就是树状数组位置p的前缀和。如果p大于等于k,那么显然第k大数在1~p之间,否则第k大数在p+1~n之间。

这个二分貌似很难用左闭右开区间写出来

 #include<cstdio>
#include<algorithm>
#include<cstring>
#define lowbit(x) ((x)&(-x))
using namespace std;
int dat[],n;
int sum(int k)//前k数的前缀和
{
int ans=;
while(k>)
{
ans+=dat[k];
k-=lowbit(k);
}
return ans;
}
void add(int pos,int x)
{
while(pos<=n)
{
dat[pos]+=x;
pos+=lowbit(pos);
}
}
int kth(int k)
{
int l=,r=n,m;
while(r>l)
{
m=l+((r-l)>>);
if(sum(m)>=k)
r=m;
else
l=m+;
}
return l;
}
int xx[];
int T;
int main()
{
int i,t;
scanf("%d",&T);
while(T--)
{
memset(dat,,sizeof(dat));
scanf("%d",&n);
for(i=;i<=n;i++) add(i,);
for(i=;i<=n;i++) scanf("%d",&xx[i]);
for(i=n;i>=;i--)
{
xx[i-]+=xx[i]/(n-i+);
xx[i]%=(n-i+);
}
for(i=;i<n;i++)
{
t=kth(xx[i]+);
printf("%d ",t);
add(t,-);
}
printf("%d\n",kth(xx[n]+));
}
return ;
}

还有一个log的写法

例如现在有一个数列1 2 3 3 4 5 7 8 9 9
值域数组a为1 1 2 1 1 0 1 1 2
c(树状数组直接存的值)为1 2 2 5 1 1 1 8 2
先找到小于第k大的数的最大数,也就是sum(x)<k的最大的x
(找第7大,k=7,答案x=5(101(2)))
一开始x=0,cnt(记录这个数之前已经累加的sum)=0
那么从第4位开始判,x+2^4>=n,所以啥也不干
x+2^3<n,cnt+c[x+2^3]=8 >= 7 所以啥也不干
x+2^2<n,cnt+c[x+2^2]=5 <7 所以 cnt+=c[x+2^2],x+=2^2 cnt=5,x=4
x+2^1<n, cnt+c[x+2^1]=7 >=7 所以啥也不干
x+2^0<n, cnt+c[x+2^0]=6 < 7 所以 cnt+=c[x+2^0],x+=2^0 cnt=6,x=5
原因:
记当前处理的位为i(也就是x+2^i,c[x+2^i]),那么每一次处理前x显然满足转换为二进制后从低位开始数前i+1位没有1(从高位开始处理,每次只加2^x,因此高位只可能在i+1位之后产生过1)
那么,根据树状数组的定义,c[x+2^i]就是a[x+1]加到a[x+2^i]的和
再参考一下这个:
求第K小的值。a[i]表示值为i的个数,c[i]当然就是管辖区域内a[i]的和了。
神奇的方法。不断逼近。每次判断是否包括(ans,ans + 1 << i]的区域,
不是的话减掉,是的话当前的值加上该区域有的元素。

http://blog.csdn.net/z309241990/article/details/9623885

 #include<cstdio>
#include<algorithm>
#include<cstring>
#define lowbit(x) ((x)&(-x))
using namespace std;
int dat[],n,n2;
//n2为值域,此处与n相同
void add(int pos,int x)
{
while(pos<=n2)
{
dat[pos]+=x;
pos+=lowbit(pos);
}
}
int kth(int k)
{
int x=,cnt=,i;
for(i=;i>=;i--)
{
x+=(<<i);
if(x>=n2||cnt+dat[x]>=k) x-=(<<i);
else cnt+=dat[x];
}
return x+;
}
int xx[];
int T;
int main()
{
int i,t;
scanf("%d",&T);
while(T--)
{
memset(dat,,sizeof(dat));
scanf("%d",&n);n2=n;
for(i=;i<=n;i++) add(i,);
for(i=;i<=n;i++) scanf("%d",&xx[i]);
for(i=n;i>=;i--)
{
xx[i-]+=xx[i]/(n-i+);
xx[i]%=(n-i+);
}
for(i=;i<n;i++)
{
t=kth(xx[i]+);
printf("%d ",t);
add(t,-);
}
printf("%d\n",kth(xx[n]+));
}
return ;
}

还有值域线段树做法,建树与树状数组类似。查找k大也是一个log,方法如下:

求区间第K大可以用线段树,以值为区间建树,区间和表示这段区间的数出现了多少次,然后就从根节点开始,
根据左子树与k的大小比较选择向左向右查找,最终到达的叶子就是我们要找的第k大的值

参考:http://blog.csdn.net/qq_38678604/article/details/78575672

曾经错误:线段树清空出错,少了20行,WA

 #include<cstdio>
#include<algorithm>
#define lc (num<<1)
#define rc (num<<1|1)
#define mid ((l+r)>>1)
typedef int LL; LL tree[],laz[];
LL L,R,x,n,m;
void build(LL l,LL r,LL num)
{
if(l==r)
{
tree[num]=;
return;
}
build(l,mid,lc);
build(mid+,r,rc);
tree[num]=tree[lc]+tree[rc];
laz[num]=;
}
void pushdown(LL l,LL r,LL num)
{
if(laz[num])
{
laz[lc]+=laz[num];
laz[rc]+=laz[num];
tree[lc]+=(mid-l+)*laz[num];
tree[rc]+=(r-mid)*laz[num];
laz[num]=;
}
}
void update(LL l,LL r,LL num)
{
if(L<=l&&r<=R)
{
tree[num]+=(r-l+)*x;
laz[num]+=x;
return;
}
pushdown(l,r,num);
if(L<=mid) update(l,mid,lc);
if(mid<R) update(mid+,r,rc);//if(mid+1<=R)
/*important*/tree[num]=tree[lc]+tree[rc];
}
LL query(LL l,LL r,LL num)
{
if(L<=l&&r<=R) return tree[num];
pushdown(l,r,num);
LL ans=;
if(L<=mid) ans+=query(l,mid,lc);
if(mid<R) ans+=query(mid+,r,rc);
return ans;
}
LL kth(LL l,LL r,LL num,LL k)
{
if(l==r) return l;
pushdown(l,mid,lc);
pushdown(mid+,r,rc);
if(tree[lc]>=k) return kth(l,mid,lc,k);
else return kth(mid+,r,rc,k-tree[lc]);
}
LL xx[];
int T;
int main()
{
LL i,t;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
build(,n,);
for(i=;i<=n;i++) scanf("%d",&xx[i]);
for(i=n;i>=;i--)
{
xx[i-]+=xx[i]/(n-i+);
xx[i]%=(n-i+);
}
for(i=;i<n;i++)
{
pushdown(,n,);
t=kth(,n,,xx[i]+);
printf("%d ",t);
L=R=t;x=-;
update(,n,);
}
pushdown(,n,);
printf("%d\n",kth(,n,,xx[n]+));
}
return ;
}

Permutation UVA - 11525(值域树状数组,树状数组区间第k大(离线),log方,log)(值域线段树第k大)的更多相关文章

  1. HDU - 1166 敌兵布阵 方法一:(线段树+单点修改,区间查询和) 方法二:利用树状数组

    C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了.A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况.由于 ...

  2. 【BZOJ-2892&1171】强袭作战&大sz的游戏 权值线段树+单调队列+标记永久化+DP

    2892: 强袭作战 Time Limit: 50 Sec  Memory Limit: 512 MBSubmit: 45  Solved: 30[Submit][Status][Discuss] D ...

  3. 洛谷P3586 [POI2015]LOG(贪心 权值线段树)

    题意 题目链接 Sol 显然整个序列的形态对询问没什么影响 设权值\(>=s\)的有\(k\)个. 我们可以让这些数每次都被选择 那么剩下的数,假设值为\(a_i\)次,则可以\(a_i\)次被 ...

  4. 【离线 撤销并查集 线段树分治】bzoj1018: [SHOI2008]堵塞的交通traffic

    本题可化成更一般的问题:离线动态图询问连通性 当然可以利用它的特殊性质,采用在线线段树维护一些标记的方法 Description 有一天,由于某种穿越现象作用,你来到了传说中的小人国.小人国的布局非常 ...

  5. 区间前k小的和(权值线段树+离散化)--2019牛客多校第7场C--砍树

    题目链接:https://ac.nowcoder.com/acm/contest/887/C?&headNav=acm 题意: 给你 n 种树,有 高度,花费和数量 ,现在问你最少需要花多少钱 ...

  6. Codeforces Round #463 F. Escape Through Leaf (李超线段树合并)

    听说正解是啥 set启发式合并+维护凸包+二分 根本不会啊 , 只会 李超线段树合并 啦 ... 题意 给你一颗有 \(n\) 个点的树 , 每个节点有两个权值 \(a_i, b_i\) . 从 \( ...

  7. 【bzoj1594-猜数游戏】线段树

    题解: 矛盾只有两种情况: 一.先前确定了x在区间(l,r),但是现在发现x在区间(l1,r1),并且两个区间不相交. 二.一个区间的最小值是x,这个区间中有一个子区间的最小值比x更小. 首先可以明确 ...

  8. UVA 11990 ”Dynamic“ Inversion(线段树+树状数组)

    [题目链接] UVA11990 [题目大意] 给出一个数列,每次删去一个数,求一个数删去之前整个数列的逆序对数. [题解] 一开始可以用树状数组统计出现的逆序对数量 对于每个删去的数,我们可以用线段树 ...

  9. uva 11525(线段树)

    题目链接:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem& ...

随机推荐

  1. 简述HashMap和Hashtable的差别

    1.HashMap继承AbstractMap类. Hashtable继承了Dictionary类. 2.HashMap同意有null的键和值.       Hashtable不同意有null的键和值. ...

  2. centos 5.11修改ssh默认端口号

    查看下服务器端口号范围: # sysctl -a|grep ip_local_port_range    net.ipv4.ip_local_port_range = 32768    61000 新 ...

  3. 百万级PHP网站Poppen.de的架构分享心得

    在了解过世界最大的PHP站点,Facebook的后台技术后, 今天我们来了解一个百万级PHP站点的网站架构:Poppen.de.Poppen.de是德国的一个社交网站,相对Facebook.Flick ...

  4. ubuntu 安装后要做的事情

    1. 安装chrome,软件中心就可以. 2. 安装vim 和一些插件.这里引入一大牛配置的插件集 sudo apt-get install vim-gtk wget -qO- https://raw ...

  5. 笔记本 ThinkPad E40 安装 Mac OS X 10.9.3 Mavericks 系统

    关于:自己最早接触Mac OS X系统是在一个论坛里.记得好像是2011年:那时论坛里就有人在虚拟机上执行Mac OS X 10.7系统.当时也依照论坛里的方法在虚拟机上成功装上了系统.那时開始就被苹 ...

  6. How to: Use Submix Voices

    How to: Use Submix Voices:https://msdn.microsoft.com/en-us/library/windows/desktop/ee415794(v=vs.85) ...

  7. 【java】itoo项目实战之hibernate 懒载入优化性能

    在做itoo 3.0 的时候,考评系统想要上线,就開始导入数据了,仅仅导入学生2万条数据,可是导入的速度特别的慢.这个慢的原因是由于导入的时候进行了过多的IO操作.可是导入成功之后,查询学生的速度更加 ...

  8. OpenGL的版本号历史和发展

    来源请注明.本文永久地址为http://www.cnblogs.com/vertexshader/articles/2917540.html OpenGL®作为业界最为广泛使用的2D和3D图形接口标准 ...

  9. 2016/05/16 UEditor 文本编辑器 使用教程与使用方法

    第一:百度UEditor编辑器的官方下载地址 ueditor 官方地址:http://ueditor.baidu.com/website/index.html 开发文档地址:http://uedito ...

  10. windows安装SVN服务器并设置开机启动

    1.安装SVN服务器,到http://subversion.apache.org/packages.html上下载windows版的SVN,并安装,在命令行下运行svn命令,如下所以,则svn服务器安 ...