Permutation UVA - 11525(值域树状数组,树状数组区间第k大(离线),log方,log)(值域线段树第k大)
看康托展开
题目给出的式子(n=s[1]*(k-1)!+s[2]*(k-2)!+...+s[k]*0!)非常像逆康托展开(将n个数的所有排列按字典序排序,并将所有排列编号(从0开始),给出排列的编号得到对应排列)用到的式子。可以想到用逆康托展开的方法。但是需要一些变化:
for(i=n;i>=1;i--) { s[i-1]+=s[i]/(n-i+1); s[i]%=(n-i+1); }
例如: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)!个元素。
因此可以用任意一种平衡树水过去
1 #include<cstdio> 2 #include<cstdlib> 3 #include<ctime> 4 #include<algorithm> 5 using namespace std; 6 #define MAXI 2147483647 7 //http://blog.csdn.net/h348592532/article/details/52837228随机数 8 int rand1() 9 { 10 static int x=471; 11 return x=(48271LL*x+1)%2147483647; 12 } 13 struct Node 14 { 15 Node* ch[2]; 16 int r;//优先级 17 int v;//value 18 int size;//维护子树的节点个数 19 int num;//当前数字出现次数 20 int cmp(int x) const//要在当前节点的哪个子树去查找,0左1右 21 { 22 if(x==v) return -1; 23 return v<x;//x<v?0:1 24 } 25 void upd() 26 { 27 size=num; 28 if(ch[0]!=NULL) size+=ch[0]->size; 29 if(ch[1]!=NULL) size+=ch[1]->size; 30 } 31 }nodes[200100]; 32 int mem,n; 33 Node* root=NULL; 34 void rotate(Node* &o,int d) 35 { 36 Node* t=o->ch[d^1];o->ch[d^1]=t->ch[d];t->ch[d]=o; 37 o->upd();t->upd();//o是t子节点,一定要这个顺序upd 38 o=t;//将当前节点变成旋转完后新的父节点 39 } 40 Node* getnode() 41 { 42 return &nodes[mem++]; 43 } 44 void insert(Node* &o,int x) 45 { 46 if(o==NULL) 47 { 48 o=getnode();o->ch[0]=o->ch[1]=NULL; 49 o->v=x;o->r=rand1();o->num=1; 50 } 51 else 52 { 53 if(o->v==x) ++(o->num); 54 else 55 { 56 int d=o->v < x;//x < o->v?0:1 57 insert(o->ch[d],x); 58 if(o->r < o->ch[d]->r) rotate(o,d^1);//不是 x < o->ch[d]->r 59 } 60 } 61 o->upd(); 62 } 63 void remove(Node* &o,int x) 64 { 65 int d=o->cmp(x); 66 if(d==-1) 67 { 68 if(o->num > 0) 69 { 70 --(o->num); 71 } 72 if(o->num == 0) 73 { 74 if(o->ch[0]==NULL) o=o->ch[1]; 75 else if(o->ch[1]==NULL) o=o->ch[0]; 76 else 77 { 78 //int d2= o->ch[0]->r > o->ch[1]->r;//o->ch[0]->r > o->ch[1]->r ? 1:0 79 int d2=o->ch[1]->r < o->ch[0]->r;//o->ch[1]->r <= o->ch[0]->r 80 rotate(o,d2); 81 remove(o->ch[d2],x); 82 //左旋则原节点变为新节点的左子节点,右旋相反 83 } 84 } 85 } 86 else remove(o->ch[d],x); 87 if(o!=NULL) o->upd(); 88 } 89 bool find(Node* o,int x) 90 { 91 int d; 92 while(o!=NULL) 93 { 94 d=o->cmp(x); 95 if(d==-1) return 1; 96 else o=o->ch[d]; 97 } 98 return 0; 99 } 100 int kth(Node* o,int k) 101 { 102 if(o==NULL||k<=0||k > o->size) return 0; 103 int s= o->ch[0]==NULL ? 0 : o->ch[0]->size; 104 if(k>s&&k<=s+ o->num) return o->v; 105 else if(k<=s) return kth(o->ch[0],k); 106 else return kth(o->ch[1],k-s- o->num); 107 } 108 int rk(Node* o,int x) 109 { 110 int r=o->ch[0]==NULL ? 0 : o->ch[0]->size; 111 if(x==o->v) return r+1; 112 else if(x<o->v) return rk(o->ch[0],x); 113 else return r+ o->num +rk(o->ch[1],x); 114 } 115 int pre(Node* o,int x) 116 { 117 if(o==NULL) return -MAXI; 118 int d=o->cmp(x); 119 if(d<=0) return pre(o->ch[0],x); 120 else return max(o->v,pre(o->ch[1],x)); 121 } 122 int nxt(Node* o,int x) 123 { 124 if(o==NULL) return MAXI; 125 int d=o->cmp(x); 126 if(d!=0) return nxt(o->ch[1],x); 127 else return min(o->v,nxt(o->ch[0],x)); 128 } 129 int xx[50100]; 130 int T; 131 int main() 132 { 133 int i; 134 scanf("%d",&T); 135 while(T--) 136 { 137 root=NULL;mem=0; 138 scanf("%d",&n); 139 for(i=1;i<=n;i++) insert(root,i); 140 for(i=1;i<=n;i++) scanf("%d",&xx[i]); 141 for(i=n;i>=1;i--) 142 { 143 xx[i-1]+=xx[i]/(n-i+1); 144 xx[i]%=(n-i+1); 145 } 146 for(i=1;i<n;i++) 147 { 148 printf("%d ",kth(root,xx[i]+1)); 149 remove(root,kth(root,xx[i]+1)); 150 } 151 printf("%d\n",kth(root,xx[n]+1));//这题卡格式 152 remove(root,kth(root,xx[n]+1)); 153 } 154 return 0; 155 }
同样可以用树状数组做。树状数组中存某个值出现的次数。也就是说,开始的集合中,如果数字x出现了y次,就在树状数组的位置x处加y。
第k大数,就是有至少k个数小于等于它的最小数。
那么,如果要求第k大数,就二分第k大数的值p,显然可以在log的时间内求出小于等于p的数的个数q,就是树状数组位置p的前缀和。如果p大于等于k,那么显然第k大数在1~p之间,否则第k大数在p+1~n之间。
这个二分貌似很难用左闭右开区间写出来
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #define lowbit(x) ((x)&(-x)) 5 using namespace std; 6 int dat[50100],n; 7 int sum(int k)//前k数的前缀和 8 { 9 int ans=0; 10 while(k>0) 11 { 12 ans+=dat[k]; 13 k-=lowbit(k); 14 } 15 return ans; 16 } 17 void add(int pos,int x) 18 { 19 while(pos<=n) 20 { 21 dat[pos]+=x; 22 pos+=lowbit(pos); 23 } 24 } 25 int kth(int k) 26 { 27 int l=1,r=n,m; 28 while(r>l) 29 { 30 m=l+((r-l)>>1); 31 if(sum(m)>=k) 32 r=m; 33 else 34 l=m+1; 35 } 36 return l; 37 } 38 int xx[50100]; 39 int T; 40 int main() 41 { 42 int i,t; 43 scanf("%d",&T); 44 while(T--) 45 { 46 memset(dat,0,sizeof(dat)); 47 scanf("%d",&n); 48 for(i=1;i<=n;i++) add(i,1); 49 for(i=1;i<=n;i++) scanf("%d",&xx[i]); 50 for(i=n;i>=1;i--) 51 { 52 xx[i-1]+=xx[i]/(n-i+1); 53 xx[i]%=(n-i+1); 54 } 55 for(i=1;i<n;i++) 56 { 57 t=kth(xx[i]+1); 58 printf("%d ",t); 59 add(t,-1); 60 } 61 printf("%d\n",kth(xx[n]+1)); 62 } 63 return 0; 64 }
还有一个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
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #define lowbit(x) ((x)&(-x)) 5 using namespace std; 6 int dat[50100],n,n2; 7 //n2为值域,此处与n相同 8 void add(int pos,int x) 9 { 10 while(pos<=n2) 11 { 12 dat[pos]+=x; 13 pos+=lowbit(pos); 14 } 15 } 16 int kth(int k) 17 { 18 int x=0,cnt=0,i; 19 for(i=16;i>=0;i--) 20 { 21 x+=(1<<i); 22 if(x>=n2||cnt+dat[x]>=k) x-=(1<<i); 23 else cnt+=dat[x]; 24 } 25 return x+1; 26 } 27 int xx[50100]; 28 int T; 29 int main() 30 { 31 int i,t; 32 scanf("%d",&T); 33 while(T--) 34 { 35 memset(dat,0,sizeof(dat)); 36 scanf("%d",&n);n2=n; 37 for(i=1;i<=n;i++) add(i,1); 38 for(i=1;i<=n;i++) scanf("%d",&xx[i]); 39 for(i=n;i>=1;i--) 40 { 41 xx[i-1]+=xx[i]/(n-i+1); 42 xx[i]%=(n-i+1); 43 } 44 for(i=1;i<n;i++) 45 { 46 t=kth(xx[i]+1); 47 printf("%d ",t); 48 add(t,-1); 49 } 50 printf("%d\n",kth(xx[n]+1)); 51 } 52 return 0; 53 }
还有值域线段树做法,建树与树状数组类似。查找k大也是一个log,方法如下:
求区间第K大可以用线段树,以值为区间建树,区间和表示这段区间的数出现了多少次,然后就从根节点开始,
根据左子树与k的大小比较选择向左向右查找,最终到达的叶子就是我们要找的第k大的值参考:http://blog.csdn.net/qq_38678604/article/details/78575672
曾经错误:线段树清空出错,少了20行,WA
1 #include<cstdio> 2 #include<algorithm> 3 #define lc (num<<1) 4 #define rc (num<<1|1) 5 #define mid ((l+r)>>1) 6 typedef int LL; 7 8 LL tree[400100],laz[400100]; 9 LL L,R,x,n,m; 10 void build(LL l,LL r,LL num) 11 { 12 if(l==r) 13 { 14 tree[num]=1; 15 return; 16 } 17 build(l,mid,lc); 18 build(mid+1,r,rc); 19 tree[num]=tree[lc]+tree[rc]; 20 laz[num]=0; 21 } 22 void pushdown(LL l,LL r,LL num) 23 { 24 if(laz[num]) 25 { 26 laz[lc]+=laz[num]; 27 laz[rc]+=laz[num]; 28 tree[lc]+=(mid-l+1)*laz[num]; 29 tree[rc]+=(r-mid)*laz[num]; 30 laz[num]=0; 31 } 32 } 33 void update(LL l,LL r,LL num) 34 { 35 if(L<=l&&r<=R) 36 { 37 tree[num]+=(r-l+1)*x; 38 laz[num]+=x; 39 return; 40 } 41 pushdown(l,r,num); 42 if(L<=mid) update(l,mid,lc); 43 if(mid<R) update(mid+1,r,rc);//if(mid+1<=R) 44 /*important*/tree[num]=tree[lc]+tree[rc]; 45 } 46 LL query(LL l,LL r,LL num) 47 { 48 if(L<=l&&r<=R) return tree[num]; 49 pushdown(l,r,num); 50 LL ans=0; 51 if(L<=mid) ans+=query(l,mid,lc); 52 if(mid<R) ans+=query(mid+1,r,rc); 53 return ans; 54 } 55 LL kth(LL l,LL r,LL num,LL k) 56 { 57 if(l==r) return l; 58 pushdown(l,mid,lc); 59 pushdown(mid+1,r,rc); 60 if(tree[lc]>=k) return kth(l,mid,lc,k); 61 else return kth(mid+1,r,rc,k-tree[lc]); 62 } 63 LL xx[50100]; 64 int T; 65 int main() 66 { 67 LL i,t; 68 scanf("%d",&T); 69 while(T--) 70 { 71 scanf("%d",&n); 72 build(1,n,1); 73 for(i=1;i<=n;i++) scanf("%d",&xx[i]); 74 for(i=n;i>=1;i--) 75 { 76 xx[i-1]+=xx[i]/(n-i+1); 77 xx[i]%=(n-i+1); 78 } 79 for(i=1;i<n;i++) 80 { 81 pushdown(1,n,1); 82 t=kth(1,n,1,xx[i]+1); 83 printf("%d ",t); 84 L=R=t;x=-1; 85 update(1,n,1); 86 } 87 pushdown(1,n,1); 88 printf("%d\n",kth(1,n,1,xx[n]+1)); 89 } 90 return 0; 91 }