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. POJ-2240 -Arbitrage(Bellman)

    题目链接:Arbitrage 让这题坑了,精度损失的厉害.用赋值的话.直接所有变成0.00了,无奈下,我仅仅好往里输了,和POJ1860一样找正环,代码也差点儿相同,略微改改就能够了,可是这个题精度损 ...

  2. MySQL基础笔记(四) 索引

    一.什么是索引 索引(Index),可以看作一个指针,指向表里的数据.当数据库没有索引时,查找信息通常是全表扫描:使用了索引,它就会直接引导到数据在表里的准确物理位置. 优点:索引的主要目的是提高数据 ...

  3. Ubuntu 14.04正式公布,一个不眠之夜

    请看下图: 这就是Ubuntu 14.04 LTS桌面版本号的一份视图.感觉既亲切,又寻常,可是,没有什么大的变化.注意:这个Ubuntu桌面版本号要陪伴我们长达5年之久! 直到4月18日(北京时间) ...

  4. STL_算法_最小值和最大值(min_element、max_element)

    C++ Primer 学习中... 简单记录下我的学习过程 (代码为主) min_element.max_element  找最小.最大值. 非常easy没什么大作用 #include<iost ...

  5. caioj1522: [NOIP提高组2005]过河

    状态压缩的经典题. 按照一般做法,DP一维时间O(n),显然跑不过.考虑到石子较少,实际上有很长一段是一定可以跳到的,设两个石头分别在i点和j点,跳跃的路程为S到T.那么从i点可以跳到i+S到i+T. ...

  6. 并不对劲的bzoj4825:loj2018:p3721:[HNOI2017]单旋

    题目大意 spaly是一种数据结构,它是只有单旋的splay 有一个初始为空的spaly,\(m\)(\(m\leq10^5\))次操作,每个操作是以下5种中的一种: 1.向spaly中插入一个数(过 ...

  7. 【SCOI 2005】 繁忙的都市

    [题目链接] 点击打开链接 [算法] 题目描述比较繁琐,但细心观察后,发现其实就是用kruskal算法求最小生成树 [代码] #include<bits/stdc++.h> using n ...

  8. Linux设备模型 (3)

    在上文中,我们介绍到如何使用default attribute.Default attribute使用很方便,但不够灵活.比如上篇文章在Kobject一节中提到的那个例子,name和val这两个att ...

  9. 洛谷 P1344 追查坏牛奶Pollutant Control —— 最小割

    题目:https://www.luogu.org/problemnew/show/P1344 就是求最小割: 但是还要边数最小,所以把边权都*1001+1,这样原来流量部分是*1001,最大流一样的不 ...

  10. 【转】解决从jenkins打开robot framework报告会提示‘Opening Robot Framework log failed ’的问题

    最新的jenkins打开jenkins robot framework报告会提示如下 Verify that you have JavaScript enabled in your browser.  ...