排列(premu.cpp)
【题目描述】
对于一个 1 到 n 的排列,逆序数的定义为:排列中第 i 位 ai的逆序数就是 a1…ai-1中比
ai大的数的个数。另外用 pi表示 a1,…,ai的逆序数和(即 pi为逆序数的前缀和)。
若知道 n 和 pi,则就能求得原排列。
现在对于排列{ai},给出 n 和{pi},请你还原这个排列。
【输入格式】
第一行输入一个数正整数 n。
第二行输入 n 个正整数,表示 pi 。
【输出格式】
输出一行,共有 n 个数,表示排列 ai。
【样例输入】
3
0 1 2
【样例输出】
3 1 2
【样例解释】
原排列 ai 3 1 2
每个数 ai对应的逆序数 0 1 1
逆序数前缀和 pi 0 1 2
【数据范围】
对于前 10%的数据:n≤10。
对于前 30%的数据:n≤1,000。
对于 100%的数据:n≤100,000;pi≤100,000,000。
这道题比较easyeasyeasy,首先可以根据最后一位的逆序数确定最后一个数,然后倒着依次确定每个数。a[1..i]a[1..i]a[1..i]中,有 bibibi 个数比 a[i]a[i]a[i]大,则 a[i]a[i]a[i]为剩下可选择的 $i $个数中第 i−b[i]i-b[i]i−b[i]小。这里介绍几种比较经典的做法
第一种:二分+树状数组,这是理论上效率最不优秀的一种做法,然而由于树状数组的常数极小,所以跑起来甚至比某些常数大的nlognnlognnlogn的算法还要更快。
(某学长的)代码如下:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
using namespace std;
int n,pre,now,tmp,l,r,mid;
int a[100010],tree[100010];
void read(int &x)
{
x=0;
char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch))
{
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
}
int lowbit(int x)
{
return x&(-x);
}
int get(int x)
{
int ans=0;
while(x>=1)
{
ans+=tree[x];
x-=lowbit(x);
}
return ans;
}
void add(int x,int y)
{
while(x<=n)
{
tree[x]+=y;
x+=lowbit(x);
}
}
int main()
{
//freopen("premu.in","r",stdin);
read(n);
pre=0;
for(int i=1;i<=n;++i)
{
read(now);
a[i]=now-pre;
pre=now;
}
for(int i=1;i<=n;++i) add(i,1);
for(int i=n;i>=1;i--)
{
tmp=i-a[i];
l=0,r=n;
while(l+1<r)
{
mid=(l+r)>>1;
if(get(mid)<tmp) l=mid;
else r=mid;
}
a[i]=r;
add(r,-1);
}
for(int i=1;i<=n;++i) cout<<a[i]<<" ";
return 0;
}
第二种:线段树,直接维护当前逆序对数为000且最靠右的数组下标,这就是当前最大数的位置。
(ykykyk神仙的)代码如下:
#include<bits/stdc++.h>
using namespace std;
#define N 100010
#define LD (t<<1)
#define RD ((t<<1)|1)
#define INF 10000010
int n,p[N],a[N];
int l[N<<2],r[N<<2],minv[N<<2],pos[N<<2],lz[N<<2];
void pushdown(int t){
if(l[t]==r[t])return;
if(lz[t]){
lz[LD]+=lz[t];
lz[RD]+=lz[t];
minv[LD]-=lz[t];
minv[RD]-=lz[t];
lz[t]=0;
}
}
void pushup(int t){
if(minv[LD]<minv[RD]){
minv[t]=minv[LD];
pos[t]=pos[LD];
}else{
minv[t]=minv[RD];
pos[t]=pos[RD];
}
}
void build(int t,int ll,int rr){
if(ll>rr)return;
l[t]=ll;r[t]=rr;
if(ll==rr){minv[t]=p[ll];pos[t]=ll;return;}
int mid=(ll+rr)>>1;
build(LD,ll,mid);
build(RD,mid+1,rr);
pushup(t);
}
void modify1(int t,int pos){
if(l[t]==r[t]){minv[t]=INF;return;}
pushdown(t);
int mid=(l[t]+r[t])>>1;
if(pos<=mid)modify1(LD,pos);
else modify1(RD,pos);
pushup(t);
}
void modify2(int t,int ll,int rr){
if(ll>r[t]||rr<l[t])return;
pushdown(t);
if(ll<=l[t]&&r[t]<=rr){
lz[t]++;
minv[t]--;
return;
}
modify2(LD,ll,rr);
modify2(RD,ll,rr);
pushup(t);
}
int main(){
// freopen("premu.in","r",stdin);
// freopen("premu.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&p[i]);
for(int i=n;i>=1;i--)p[i]-=p[i-1];
build(1,1,n);
for(int i=n;i>=1;i--){
int tmp=pos[1];
modify1(1,tmp);
modify2(1,tmp+1,n);
a[tmp]=i;
}
for(int i=1;i<=n;i++)printf("%d ",a[i]);
return 0;
}
第三种:平衡树,将逆序对数转化为当前节点在还没有确定的数中的排名,用平衡树维护。
(zxy大佬的)代码如下:
#include<bits/stdc++.h>
#define re register
#define ll long long
#define itn int
using namespace std;
inline
ll getint(){
ll num=0;
char c;
bool f=0;
for(c=getchar();!isdigit(c);c=getchar())if(c=='-')break;
if(c=='-')f=1,c=getchar();
for(;isdigit(c);c=getchar())
num=(num<<1)+(num<<3)+c-'0';
return f?-num:num;
}
inline
void outint(ll a){
if(a<0)putchar('-'),a=-a;
if(a>9)outint(a/10);
putchar(a%10+'0');
}
const int INF=0x7ffffff;
int pool=1;
int rt=0;
struct node {
int lc, rc, key, pri, cnt, sze;
#define lc(x) t[x].lc
#define rc(x) t[x].rc
#define v(x) t[x].key
#define p(x) t[x].pri
#define c(x) t[x].cnt
#define s(x) t[x].sze
} t[200005];
inline void upt(const int &k) { s(k) = s(lc(k)) + s(rc(k)) + c(k); }
inline void Zig(int &k) {
int y = lc(k);
lc(k) = rc(y);
rc(y) = k;
s(y) = s(k);
upt(k);k = y;
}
inline void Zag(int &k) {
int y = rc(k);
rc(k) = lc(y);
lc(y) = k;
s(y) = s(k);
upt(k);
k = y;
}
inline void Insert(int &k, const int &key) {
if (!k) {
k = ++pool; v(k) = key; p(k) = rand();
c(k) = s(k) = 1; lc(k) = rc(k) = 0;
return ;
}
else ++s(k);
if (v(k) == key) ++c(k);
else if (key < v(k)) {
Insert(lc(k), key);
if (p(lc(k)) < p(k)) Zig(k);
} else {
Insert(rc(k), key);
if (p(rc(k)) < p(k)) Zag(k);
}
return ;
}
inline void Delete(int &k, const int &key) {
if (v(k) == key) {
if (c(k) > 1) --c(k), --s(k);
else if (!lc(k) || !rc(k)) k = lc(k) + rc(k);
else if (p(lc(k)) < p(rc(k))) Zig(k), Delete(k, key);
else Zag(k), Delete(k, key);
return ;
}
else --s(k);
if (key < v(k)) Delete(lc(k), key);
else Delete(rc(k), key);
return ;
}
inline int QueryRank(const int &key) {
int x = rt, res = 0;
while (x) {
if (key == v(x)) return res + s(lc(x)) + 1;
if (key < v(x)) x = lc(x);
else res += s(lc(x)) + c(x), x = rc(x);
}
return res;
}
inline int QueryKth(int k) {
int x = rt;
while (x) {
if (s(lc(x)) < k && s(lc(x)) + c(x) >= k) return v(x);
if (s(lc(x)) >= k) x = lc(x);
else k -= s(lc(x)) + c(x), x = rc(x);
}
return 0;
}
inline int QueryPre(const int &key) {
int x = rt, res = -INF;
while (x) {
if (v(x) < key) res = v(x), x = rc(x);
else x = lc(x);
}
return res;
}
inline int QuerySuf(const int &key) {
int x = rt, res = INF;
while (x) {
if (v(x) > key) res = v(x), x = lc(x);
else x = rc(x);
}
return res;
}
int n;
int ans[100005];
int pre[100005];
int rank[100005];
int main(){
n=getint();
for(int re i=1;i<=n;++i)pre[i]=getint();
for(int re i=n;i>=1;--i)pre[i]=pre[i]-pre[i-1];
for(int re i=1;i<=n;++i)rank[i]=i-pre[i];
for(int re i=1;i<=n;++i)Insert(rt,i);
for(int re i=n;i>=1;i--){
ans[i]=QueryKth(rank[i]);
Delete(rt,ans[i]);
}
for(int re i=1;i<=n;++i){
outint(ans[i]),putchar(' ');
}
return 0;
}
第四种:线段树,思想同平衡树,具体实现就是维护一段最靠左的前缀和。
(本蒟蒻的)代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#define lc (p<<1)
#define rc (p<<1|1)
#define mid (T[p].l+T[p].r>>1)
#define N 100005
using namespace std;
inline long long read(){
long long ans=0,w=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')w=-1;
ch=getchar();
}
while(isdigit(ch))ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar();
return ans*w;
}
int p[N],a[N],ans[N],n;
struct Node{int l,r,sum;}T[N<<2];
inline void pushup(int p){T[p].sum=T[lc].sum+T[rc].sum;}
inline void build(int p,int l,int r){
T[p].l=l,T[p].r=r;
if(l==r){T[p].sum=1;return;}
build(lc,l,mid);
build(rc,mid+1,r);
pushup(p);
}
inline void update(int p,int k){
if(T[p].l==T[p].r){
T[p].sum=0;
return;
}
if(k<=mid)update(lc,k);
else update(rc,k);
pushup(p);
}
inline int query(int p,int v){
if(T[p].l==T[p].r)return T[p].l;
if(T[lc].sum>=v)return query(lc,v);
else return query(rc,v-T[lc].sum);
}
int main(){
// freopen("premu.in","r",stdin);
// freopen("premu.out","w",stdout);
n=read();
for(int i=1;i<=n;++i)p[i]=read(),a[i]=i-(p[i]-p[i-1]);
build(1,1,n);
for(int i=n;i>=1;--i){
ans[i]=query(1,a[i]);
update(1,ans[i]);
}
for(int i=1;i<=n;++i)printf("%d ",ans[i]);
return 0;
}