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. StringUtil内部方法差异

    StringUtil 的 isBlank.isEmply.isNotEmpty.isNotBlank 区别   String.trim()方法: trim()是去掉首尾空格   append(Stri ...

  2. 深入浅出Redis(二)高级特性:事务

    第一篇中介绍了Redis是一个强大的键-值仓储,支持五种灵活的数据结构.其实,Redis还支持其他的一些高级特性:事务.公布与订阅.管道.脚本等,本篇我们来看一下事务. 前一篇中我们提到,在Redis ...

  3. [原创+分享]Mandelbrot Explorer

    Mandelbrot Explorer 是一款用于在MandelBort集/Julia集上进行无限漫游的软件,使用VS2013+CUDA6.5开发而成.它也是我学习CUDA开发的一个小小的成果,欢迎大 ...

  4. 开发:异常收集之 DB2建表相关问题

    第一次用DB2数据库,因为考虑到建表语句可能不一样,所以採用手动建表的办法.一个个字段去填.并勾选主键.最后发现创建失败.看了下系统生成的sql语句 sql语句例如以下: CREATE TABLE F ...

  5. Redis管理各类型存储数据命令

    >>>字符串 1 SET key value 设置指定 key 的值 2 GET key 获取指定 key 的值. 3 GETRANGE key start end 返回 key 中 ...

  6. angularjs学习之六(angularjs中directive指令的一般编程事件绑定 模板使用等)

    angular js 中模板的使用.事件绑定以及指令与指令之间的交互 相应教学视频地址(需FQ):v=aG8VD0KvUw4">angularjs教学视频 <!doctype h ...

  7. 【iOS系列】-xib封装使用

    [iOS系列]-xib封装使用 Xib文件可以用来描述某一块局部的UI界面 Xib文件的加载 修改xib文件的大小size(Freeform) 第一: NSArray *objs = [[NSBund ...

  8. [办公自动化]chrome浏览器的书签在哪里存放

    最近换电脑了. 硬盘直接挂在了新电脑上.忘记导出Chrome的浏览器的书签了. 对于书签,Windows XP和Windows 7的路径都是: C:\Documents and Settings\** ...

  9. Django框架之ORM

    1,字段和字段的参数 1.1>ORM的概念:对象映射模型(Objects Relational Model)是一种为了解决面向对象和关系型数据库存在的互不匹配的现象和技术,简单的说,ORM是通过 ...

  10. unix2dos/dos2unix

    dos2unix命令用来将DOS格式的文本文件转换成UNIX格式的(DOS/MAC to UNIX text file format converter).DOS下的文本文件是以\r\n作为断行标志的 ...