看康托展开
题目给出的式子(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.****.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.****.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.****.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 ;
}