Examples

2022-6-23 #4 CF1558F & CF1693F

今天好颓啊😴。

007 CF1558F Strange Sort

这种排序的题 ,有一个套路:“钦定一个 \(x\),将大于等于 \(x\) 的数字设为 \(1\),否则设为 \(0\)”。

这样我们就不关心 \(0\) 之间的顺序,那么第 \(i\)\(0\) 最后会到达位置 \(i\)

于是我们从小到大扫描 \(x\),每次将一个 \(1\) 变成 \(0\),只需维护每个 \(0\) 到达其最后位置的时间,求出每个 \(x\) 的全局最大答案再取 \(\max\) 即可。

对于第 \(i\)\(0\)(令它的位置为 \(z_i\)),令 \(p_i\) 为它对应的答案,那么可以列出:

\[p_i=\max\{p_{i-1}+1,(z_i\bmod 2)+\sum_{j=1}^{z_i}[a_j=1]\} \]

也就是说,答案为:(令 \(zs\)\(0\) 的数量)

\[\max_i\{(z_i\bmod 2)+\sum_{j=1}^{z_i}[a_j=1]+(zs-i)\} \]

变一下形,每个为零的位置维护一下:

\[\max_i\{i+(i\bmod 2)+zs-2\sum_{j=1}^i[a_j=0]\} \]

注意如果一个点所在的前缀都是 \(0\),我们不能计算它的贡献,我们维护一个指针就好了,剩下的部分线段树维护即可,复杂度 \(O(n\log n)\)

#include<stdio.h>
#define lc(x) (x<<1)
#define rc(x) (x<<1|1)
#define mid (l+r>>1)
#define inf 1000000000
const int maxn=200005,maxt=maxn<<2;
int T,n,ans;
int p[maxn],q[maxn],mx[maxt],lazy[maxt],vis[maxn];
inline int max(int a,int b){
	return a>b? a:b;
}
inline void pushup(int now){
	mx[now]=max(mx[lc(now)],mx[rc(now)]);
}
inline void getlazy(int now,int v){
	lazy[now]+=v,mx[now]+=v;
}
inline void pushdown(int now){
	if(lazy[now])
		getlazy(lc(now),lazy[now]),getlazy(rc(now),lazy[now]),lazy[now]=0;
}
void build(int l,int r,int now){
	lazy[now]=0;
	if(l==r){
		mx[now]=l+(l&1)-inf;
		return ;
	}
	build(l,mid,lc(now)),build(mid+1,r,rc(now)),pushup(now);
}
void update(int l,int r,int now,int L,int R,int v){
	if(L>R)
		return ;
	if(L<=l&&r<=R){
		getlazy(now,v);
		return ;
	}
	pushdown(now);
	if(L<=mid)
		update(l,mid,lc(now),L,R,v);
	if(mid<R)
		update(mid+1,r,rc(now),L,R,v);
	pushup(now);
}
int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n),ans=0;
		for(int i=1;i<=n;i++)
			scanf("%d",&p[i]),q[p[i]]=i;
		build(1,n,1);
		for(int i=1,j=1;i<=n;i++){
			int x=q[i];
			update(1,n,1,x,x,inf),update(1,n,1,x,n,-2);
			vis[x]=1;
			while(j<=n&&vis[j])
				update(1,n,1,j,j,-inf),j++;
			ans=max(ans,mx[1]+i);
		}
		printf("%d\n",ans);
		for(int i=1;i<=n;i++)
			vis[i]=0;
	}
	return 0;
}

008 CF1693F I Might Be Wrong

很厉害的题啊!

有一个结论是操作一定会满足 \(cnt_0=cnt_1\),我们只需证明任意一个代价为 \(x\) 的操作都可以用不超过 \(x\) 次这样的操作代替即可。

将这个子段提取出来,将 \(0\) 看成 \(-1\)\(1\) 看成 \(1\),考察其前缀和对应格路,我们从高到低扫,若当前高度有大于一个位置,就将最前面的位置和最后面的位置进行一次操作。正确性感性理解一下就好了!!!而操作次数就是整个段的和的绝对值加一,不超过其操作一次的代价。

一个做法是继续观察性质:(不妨令序列总和小于 \(0\),如果不满足就可以将 \(01\) swap,然后给串 reverse)

  1. 操作的高度必须在起点与终点之间。(如果大于,则可以找到一个高度等于起点、终点高度最大值的区间包含这个操作的区间,反之亦然。)
  2. 如果操作的时候,操作左端点后面是一个下步,则可以省略这个操作。

哈哈,破防了,看不懂这个做法。

另一个做法是直接对这个这个东西贪心,我们对于每个 \(x\) 维护前缀和为 \(x\) 的最长前缀,然后贪心地向后跳,如果跳不了就补一些 \(0\) 进去。

这样是 \(O(n)\) 的。

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=200005;
int T,n,ans;
int sum[maxn],rec[maxn];
string s;
void calc(){
	for(int i=1;i<=n;i++){
		sum[i]=sum[i-1]+(s[i-1]=='0'? 1:-1);
		if(sum[i]>=0)
			rec[sum[i]]=i;
	}
}
int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n),cin>>s;
		int flg=0;
		for(int i=1;i<n;i++)
			if(s[i]<s[i-1])
				flg=1;
		if(flg==0){
			puts("0");
			continue;
		}
		calc();
		if(sum[n]<0){
			reverse(s.begin(),s.end());
			for(int i=0;i<n;i++)
				s[i]^=1;
			calc();
		}
		int now=0;
		while(now<n&&s[now]=='0')
			now++;
		ans=0;
		while(now<sum[n])
			ans++,now+=(rec[now]-now)/2;
		printf("%d\n",ans+1);
		for(int i=0;i<=n;i++)
			rec[i]=0;
	}
	return 0;
}

009 CF1693E Outermost Maximums

ecnerwala 提供了一种很好写的解法。

可以发现,对于一个最大值位置,我们令 \(l_i,r_i\) 分别代表它前面的小于最大值的最大值,后面的小于最大值的最大值,那么这个位置一定会贪心地变为 \(\min\{l_i,r_i\}\)

由于 \(l,r\) 单调,我们总能找到一个分割点使得其前面都有 \(l_i\leqslant r_i\),分割点前从前往后操作,分割点后从后往前即可使每个位置都到达下界。

现在我们要加速这个过程,我们考虑延迟维护这个 \(l,r\)

从大到小加入每个数,当一个数被加入时我们先不决策它选择 \(l\) 还是 \(r\)。在加入完一个值后,令这个值在序列上形成的区间为 \([l,r]\),我们将其左侧没有决策的数决策为选择 \(l\),并将决策了 \(r\) 的数加入这个区间,右侧同理。

上述算法正确性显然,而且没有决策的位置的位置中间一定不会夹着决策了的位置,我们只需维护这一区间,再用个树状数组记录哪些点被加入了就好了。

复杂度 \(O(n\log n)\)

#include<stdio.h>
#include<vector>
#define lowbit(x) (x&-x)
using namespace std;
const int maxn=200005;
int n;
int a[maxn],t[maxn];
vector<int>v[maxn];
long long ans;
void update(int x,int v){
	for(int i=x;i<=n;i+=lowbit(i))
		t[i]+=v;
}
int query(int x){
	int res=0;
	for(int i=x;i;i-=lowbit(i))
		res+=t[i];
	return res;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]),v[a[i]].push_back(i);
	int lstL=n+1,lstR=0;
	for(int i=n;i>=1;i--)
		if(v[i].size()){
			for(int j=0;j<v[i].size();j++)
				update(v[i][j],1);
			int L=v[i][0],R=v[i].back();
			if(lstR<L)
				L=lstR+1;
			if(lstL>R)
				R=lstL-1;
			ans+=query(R)-query(L-1);
			lstL=L,lstR=R;
		}
	printf("%lld\n",ans);
	return 0;
}
posted @ 2022-06-23 14:55  xiaoziyao  阅读(112)  评论(0编辑  收藏  举报