【2022省选模拟】小 Z 与函数——结论,树状数组,STL

又是一道没有来源的题

题目描述

在这里插入图片描述
对于 15% 的数据,满足 n ≤ 300 n\le 300 n300;
对于 50% 的数据,满足 n ≤ 3000 n\le 3000 n3000;
对于另外 35% 的数据,满足 a a a 是个排列。

题解

ZXY yydBUS!!

首先考虑是排列的情况怎么做:容易发现每次交换操作恰好减少一个正序对,所以swap的总次数等于正序对个数。
除了swap次数,这题还要求vs的和。我们考虑哪些位置会轮空(即vs=0),发现如果存在 j ≤ i < k j\le i<k ji<k 使得 a j < a k a_j<a_k aj<ak,那么 i i i 处不可能轮空,反之一定轮空。
所以我们只需用树状数组维护第一部分,用单调栈维护第二部分即可,配合纯暴力可以拿50分。

但是一旦出现了相同的数,上面的结论就会出问题。

对于swap次数,如果我们规定只算每种元素第一个位置后面大于它的数的个数为正序对,那么结论仍然是成立的。

对于vs的和,上面的结论就完全扩展不过来了。

我们考虑每次在序列末尾加入一个数的时候,操作会发生什么变化。对于前面的数组,肯定是已经降序排序了的。假设我们加入的这个数为 x x x,找到第一个 < x <x <x 的位置 i i i,那么 i i i 处必定会被替换为 x x x;由于 i i i 处的元素必定是 i ∼ n i\sim n in 的最大值,所以它会跑到数组末尾去,然后又变成了一个子问题,找第一个 < a i <a_i <ai 的位置…

我们发现,每次插入一个元素 x x x,相当于找到了一条极长下降子序列,然后把 x x x 放最开头,其它位置顺次往后挪。这个极长下降子序列其实就是 i i i 后面的每段相同元素的第一个位置,所以这个子序列往后挪其实等价于 i ∼ n i\sim n in 整体往后挪。这个时候考虑轮空的位置,我们发现这个下降子序列上的位置必定不轮空,并且一个位置只会从轮空变为不轮空。

接下来只需要考虑用数据结构优化这个过程。我们可以用set或者map存每一个轮空并且为相同元素段段首的位置,那么我们每加入一个元素时,枚举到的位置都会打上标记,要么挪向下一个没打标记的位置,要么删除,复杂度是均摊 O ( log ⁡ n ) O(\log n) O(logn) 的。由于插入一个 x x x 会让一段后缀的元素整体往后挪,所以我们用一个树状数组维护rank即可。

代码

由于map里第二维记录了rank,省去了再查一遍rank的时间,所以这是目前最快的一发

#include<bits/stdc++.h>//JZM yyds!!
#define ll long long
#define uns unsigned
#define IF (it->first)
#define IS (it->second)
#define END putchar('\n')
using namespace std;
const int MAXN=200005;
const ll INF=1e18;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
int ptf[50],lpt;
inline void print(ll x,char c='\n'){
	if(x<0)putchar('-'),x=-x;
	ptf[lpt=1]=x%10;
	while(x>9)x/=10,ptf[++lpt]=x%10;
	while(lpt)putchar(ptf[lpt--]^48);
	if(c>0)putchar(c);
}
inline ll lowbit(ll x){return x&-x;}

int n,a[MAXN],f[MAXN],g[MAXN],dl[MAXN],le;
bool b[MAXN],vis[MAXN];
inline void add(int x,int*F){
	for(;x<=n;x+=lowbit(x))F[x]++;
}
inline int sum(int x,int*F){
	int res=0;
	for(;x>0;x^=lowbit(x))res+=F[x];
	return res;
}
map<int,int>mp;
signed main()
{
	freopen("function.in","r",stdin);
	freopen("function.out","w",stdout);
	for(int CASE=read();CASE--;){
		n=read();
		for(int i=1;i<=n;i++)a[i]=read(),b[i]=vis[i]=0,f[i]=g[i]=0;
		ll ans=0;
		mp.clear();
		for(int i=1;i<=n;i++){
			ans+=sum(a[i]-1,f),le=0,add(a[i],g);
			if(!vis[a[i]])add(a[i],f),vis[a[i]]=1;
			for(auto it=mp.begin();it!=mp.end();it++){
				if(IF>=a[i])break;
				ans++,b[IS]=1,IS++;
				if(b[IS])dl[++le]=IF;
			}while(le)mp.erase(dl[le--]);
			int x=i-sum(a[i]-1,g);
			if(x==i){if(!mp[a[i]])mp[a[i]]=x;}
			else ans+=(!b[x]),b[x]=1;
			print(ans,i<n?' ':'\n');
		}
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值