bzoj 3333: 排队计划(树状数组+线段树)

3333: 排队计划

Time Limit: 20 Sec   Memory Limit: 128 MB
Submit: 405   Solved: 194
[ Submit][ Status][ Discuss]

Description

Input

Output

Sample Input

6 2
160 163 164 161 167 160
2
3

Sample Output

6
3
1

HINT

Source

[ Submit][ Status][ Discuss]


题解:树状数组+线段树

首先利用权值树状数组求出以i个点开头的逆序对数。

选出一个位置i,将i-n中所有小于等于p[i]的位置选出来重新排好序后放回,可以发现这样做对于位于i前面的点的逆序对数没有影响,因为原先在后面现在还在后面,对于i-n中大于p[i]的点的逆序对数也没有影响因为无论怎么移动,选出的位置一定小于他。那么有影响的其实就是选出了的点,他们的逆序对数变为了0。

用线段树维护区间最小值所在的位置(如果两个位置的值一样,那么优先选择后面的位置),每次将一个位置的逆序对数清零后,将这个位置的数值付成极大值,这样就可以保证每个点至多修改一次,然后从i-n的区间中一直修改,知道a[i]=inf位置,输出此时序列中的总逆序对数。时间复杂度应该在o(nlogn)左右。

刚开始还想到了树套数的写法,用区间线段树套权值线段树,貌似也可以,但是时间复杂度不如这种方法优越。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 500003
#define LL long long 
#define inf 1000000000
using namespace std;
int n,m;
int a[N],b[N],p[N],cnt;
LL f[N],tr[N*4],ans;
int tree[N*4];
int cmp(int x,int y)
{
	return b[x]<b[y];
}
int lowbit(int x)
{
	return x&(-x);
}
void change(int x,LL v)
{
	for (int i=x;i<=cnt;i+=lowbit(i))
	 tr[i]+=v;
}
LL sum(int x)
{
	LL ans=0;
	for (int i=x;i;i-=lowbit(i))
	 ans+=tr[i];
	return ans;
}
void update(int x)
{
	if (a[tree[x<<1|1]]<=a[tree[x<<1]])
	 tree[x]=tree[x<<1|1];
	else
	 tree[x]=tree[x<<1];
}
void build(int now,int l,int r)
{
	if (l==r)
	 {
	 	tree[now]=l;
	 	return;
	 }
	int mid=(l+r)/2;
	build(now<<1,l,mid);
	build(now<<1|1,mid+1,r);
	update(now);
}
int pd(int x,int y)
{
	if (a[y]<=a[x])  return y;
	else return x;
}
int query(int now,int l,int r,int ll,int rr)
{
	if (l>=ll&&r<=rr)
	 return tree[now];
	int mid=(l+r)/2;
	int ans=0;
	if (ll<=mid)
	  ans=query(now<<1,l,mid,ll,rr);
	if (rr>mid)
	 {
	 	int t=query(now<<1|1,mid+1,r,ll,rr);
	 	if (ans==0) ans=t;
	 	else ans=pd(ans,t);
	 }
	return ans;
}
void pointchange(int now,int l,int r,int x)
{
	if (l==r)
	{
		a[l]=inf;
		f[l]=0; 
		return ;
	}
	int mid=(l+r)/2;
	if (x<=mid)
	 pointchange(now<<1,l,mid,x);
	else
	 pointchange(now<<1|1,mid+1,r,x);
	update(now);
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	 scanf("%d",&b[i]),p[i]=i;
	sort(p+1,p+n+1,cmp);
	cnt=0;
	for (int i=1;i<=n;i++)
	 if (b[p[i]]!=b[p[i-1]]) a[p[i]]=++cnt;
	 else a[p[i]]=cnt;
	for (int i=n;i>=1;i--)
	  {
	  	change(a[i],1);
	  	f[i]=sum(a[i]-1); 
	  	ans+=f[i];
	  }
	printf("%lld\n",ans);
	build(1,1,n);
	for (int i=1;i<=m;i++)
	 {
	 	int x; scanf("%d",&x);
	 	while (a[x]!=inf)
	 	 {
	 	 	int t=query(1,1,n,x,n);
	 	 	ans-=f[t];
	 	 	pointchange(1,1,n,t);
	 	 }
	 	printf("%lld\n",ans);
	 }
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值