2018.06.29 NOIP模拟 排列(线段树)

排列(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]ib[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;
}

转载于:https://www.cnblogs.com/ldxcaicai/p/9738556.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值