Permutation UVA - 11525(值域树状数组,树状数组区间第k大(离线),log方,log)

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\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 }

还有线段树做法?

时间: 2024-08-25 11:47:01

Permutation UVA - 11525(值域树状数组,树状数组区间第k大(离线),log方,log)的相关文章

POJ2104-- K-th Number(主席树静态区间第k大)

[转载]一篇还算可以的文章,关于可持久化线段树http://finaltheory.info/?p=249 无修改的区间第K大 我们先考虑简化的问题:我们要询问整个区间内的第K大.这样我们对值域建线段树,每个节点记录这个区间所包含的元素个数,建树和查询时的区间范围用递归参数传递,然后用二叉查找树的询问方式即可:即如果左边元素个数sum>=K,递归查找左子树第K大,否则递归查找右子树第K – sum大,直到返回叶子的值. 现在我们要回答对于区间[l, r]的第K大询问.如果我们能够得到一个插入原序

ZOJ -2112 Dynamic Rankings 主席树 待修改的区间第K大

Dynamic Rankings 带修改的区间第K大其实就是先和静态区间第K大的操作一样.先建立一颗主席树, 然后再在树状数组的每一个节点开线段树(其实也是主席树,共用节点), 每次修改的时候都按照树状数组的方式去修改,并且修改那些地方.查询的时候就是查询原主席树+树状数组的值. 代码: 1 #include<bits/stdc++.h> 2 using namespace std; 3 #define Fopen freopen("_in.txt","r&quo

poj 2401 划分树 求区间第k大的数

题目:http://poj.org/problem?id=2104 划分树待我好好理解下再写个教程吧,觉得网上的内容一般,,, 模板题: 贴代码: #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define CLR(a) memset(a,0,sizeof(a)) const int MAXN = 1000

Poj 2104区间第k大(归并树)

题目链接 K-th Number Time Limit: 20000MS Memory Limit: 65536K Total Submissions: 36890 Accepted: 11860 Case Time Limit: 2000MS Description You are working for Macrohard company in data structures department. After failing your previous task about key ins

hdu 5919 主席树(区间不同数的个数 + 区间第k大)

Sequence II Time Limit: 9000/4500 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)Total Submission(s): 849    Accepted Submission(s): 204 Problem Description Mr. Frog has an integer sequence of length n, which can be denoted as a1,a2,?

静态区间第k大 树套树解法

然而过不去你谷的模板 思路: 值域线段树\([l,r]\)代表一棵值域在\([l,r]\)范围内的点构成的一颗平衡树 平衡树的\(BST\)权值为点在序列中的位置 查询区间第\(k\)大值时 左区间在\([l,r]\)范围内的树的大小与\(k\)比较 大了进去,小了减掉换一边 关于建树 递归建估计是\(O(nlog^2n)\)的 Code: #include <cstdio> #include <cstdlib> #include <algorithm> #includ

poj2104 求区间第k大 可持久化线段树

poj2104 求区间第k大  可持久化线段树 #include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #define REP(i,a,b) for(int i=a;i<=b;i++) #define MS0(a) memset(a,0,sizeof(a)) using namespace std; typedef

POJ 2014.K-th Number 区间第k大 (归并树)

K-th Number Time Limit: 20000MS   Memory Limit: 65536K Total Submissions: 57543   Accepted: 19893 Case Time Limit: 2000MS Description You are working for Macrohard company in data structures department. After failing your previous task about key inse

hdu 2665 可持久化线段树求区间第K大值(函数式线段树||主席树)

http://acm.hdu.edu.cn/showproblem.php?pid=2665 Problem Description Give you a sequence and ask you the kth big number of a inteval. Input The first line is the number of the test cases. For each test case, the first line contain two integer n and m (