1. 程式人生 > >Permutation UVA - 11525(值域樹狀數組,樹狀數組區間第k大(離線),log方,log)

Permutation UVA - 11525(值域樹狀數組,樹狀數組區間第k大(離線),log方,log)

一次 跳過 += 數字 div ret num while printf

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 }

還有線段樹做法?

Permutation UVA - 11525(值域樹狀數組,樹狀數組區間第k大(離線),log方,log)