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>=;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.csdn.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.csdn.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.csdn.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 ;
}
Permutation UVA - 11525(值域树状数组,树状数组区间第k大(离线),log方,log)(值域线段树第k大)的更多相关文章
- HDU - 1166 敌兵布阵 方法一:(线段树+单点修改,区间查询和) 方法二:利用树状数组
C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了.A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况.由于 ...
- 【BZOJ-2892&1171】强袭作战&大sz的游戏 权值线段树+单调队列+标记永久化+DP
2892: 强袭作战 Time Limit: 50 Sec Memory Limit: 512 MBSubmit: 45 Solved: 30[Submit][Status][Discuss] D ...
- 洛谷P3586 [POI2015]LOG(贪心 权值线段树)
题意 题目链接 Sol 显然整个序列的形态对询问没什么影响 设权值\(>=s\)的有\(k\)个. 我们可以让这些数每次都被选择 那么剩下的数,假设值为\(a_i\)次,则可以\(a_i\)次被 ...
- 【离线 撤销并查集 线段树分治】bzoj1018: [SHOI2008]堵塞的交通traffic
本题可化成更一般的问题:离线动态图询问连通性 当然可以利用它的特殊性质,采用在线线段树维护一些标记的方法 Description 有一天,由于某种穿越现象作用,你来到了传说中的小人国.小人国的布局非常 ...
- 区间前k小的和(权值线段树+离散化)--2019牛客多校第7场C--砍树
题目链接:https://ac.nowcoder.com/acm/contest/887/C?&headNav=acm 题意: 给你 n 种树,有 高度,花费和数量 ,现在问你最少需要花多少钱 ...
- Codeforces Round #463 F. Escape Through Leaf (李超线段树合并)
听说正解是啥 set启发式合并+维护凸包+二分 根本不会啊 , 只会 李超线段树合并 啦 ... 题意 给你一颗有 \(n\) 个点的树 , 每个节点有两个权值 \(a_i, b_i\) . 从 \( ...
- 【bzoj1594-猜数游戏】线段树
题解: 矛盾只有两种情况: 一.先前确定了x在区间(l,r),但是现在发现x在区间(l1,r1),并且两个区间不相交. 二.一个区间的最小值是x,这个区间中有一个子区间的最小值比x更小. 首先可以明确 ...
- UVA 11990 ”Dynamic“ Inversion(线段树+树状数组)
[题目链接] UVA11990 [题目大意] 给出一个数列,每次删去一个数,求一个数删去之前整个数列的逆序对数. [题解] 一开始可以用树状数组统计出现的逆序对数量 对于每个删去的数,我们可以用线段树 ...
- uva 11525(线段树)
题目链接:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem& ...
随机推荐
- 微信小程序之 Tabbar(底部选项卡)
1.项目目录 2.在app.json里填写:tab个数范围2-5个 app.json { "pages": [ "pages/index/index", &qu ...
- Android常见UI组件之ListView(二)——定制ListView
Android常见UI组件之ListView(二)--定制ListView 这一篇接上篇.展示ListView中选择多个项及实现筛选功能~ 1.在位于res/values目录下的strings.xml ...
- java SE基础(Collection接口概述)
Collection接口相关集成关系例如以下图 1. 关于可迭代接口(Iterable) 可迭代接口仅包括一个方法,返回一个在一组T类型元素上进行迭代的迭代器: public ...
- 移动端html5页面长按实现高亮全选文本内容的兼容解决方式
近期须要给html5的WebAPP在页面上实现一个复制功能:用户点击长按文本会全选文字并弹出系统"复制"菜单.用户能够点击"复制"进行复制操作.然后粘贴到App ...
- 基于Cocos2dx + box2d 实现的愤慨的小鸟Demo
1. Demo初始界面 2. 游戏界面 3. 精确碰撞检測 4. 下载 压缩文件文件夹 AngryBird source 愤慨的小鸟Demo源码,基于Cocos2dx C++,以及box2d技 ...
- tomcat项目重复加载问题
主要是通过配置<Tomcat安装目录>/conf/server.xml文件 步骤: 1.打开server.xml,在</Host>的上一行添加内容格式如下 <Contex ...
- MVC优缺点
1.通过把项目分成model view和controller,使得复杂项目更加容易维护. 2.没有使用view state和服务器表单控件,可以更方便的控制应用程序的行为 3.应用程序通过contro ...
- Java类加载机制?
深入研究Java类加载机制 类加载是Java程序运行的第一步,研究类的加载有助于了解JVM执行过程,并指导开发者采取更有效的措施配合程序执行. 研究类加载机制的第二个目的是让程序能动态的控制类加载,比 ...
- vs2010 创建和发布 webservice
1 打开VS2010,菜单 文件->新建->项目 2 选择[ASP.net 空web应用程序],将其命名为自己想的工程名称. 3 右键点击工程,添加->新建项 选择 web服务 ...
- Lightoj 1020 - A Childhood Game
Allice先拿,最后拿球的输. Bob先拿,最后拿球的赢. 考虑Alice先拿球,当n=1时 Alice输 记dp[1]=0; n=2, dp[2]=1 n=3, dp[3]=1 因为n=1, ...