Permutation UVA

Permutation UVA - 11525

康托展开

题目给出的式子(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
",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
",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
",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
",kth(1,n,1,xx[n]+1));
89     }
90     return 0;
91 }
原文地址:https://www.cnblogs.com/hehe54321/p/7895779.html