CF1998E2 Eliminating Balls With Merging (Hard Version)

LHLeisus發表於2024-09-02

原題連結

考慮對於每個 \(i\),算出向左擴充套件到 \(1\) 時向右至少和至多擴充套件到哪裡,記為 \(minr\)\(maxr\)

那麼也就是說每個 \(i\) 會對 \(minr\sim maxr\) 做出貢獻,差分一下就可以了。重點是怎麼計算這兩個東西。

先說 \(maxr\)。如果暴力跳,過程是:先向左擴充套件直到不能擴充套件,然後再向右擴充套件到不能擴充套件。不斷重複直到左邊界到 \(1\) 或者兩邊都無法繼續擴充套件宣告失敗。

然後你想起了剛剛做完的 E1,每次擴充套件一定是找到一個最近的最大值,把最大值之前的都加上,再看看能不能也把這個最大值也加上。這個可以用 ST 表和二分 \(\mathcal{O}(\log V\log n)\) 解決。

具體地說,記 \(l,r\) 為當前左右邊界,\(s\) 為字首和,在向左擴充套件時,要找到一個最大的 \(x\) 使得 \(s_r-s_{l-1}<a_{l-1}\),移項變為 \(s_r<a_{l-1}+s_{l-1}\),用 ST 表維護 \(a_i+s_i\) 的最大值。二分時每次檢查右區間是否符合條件,如果是則增大左邊界,否則減小右邊界。向右擴充套件基本同理,式子稍有變化,換成了維護最小值。

其實 \(maxr\) 也可以雙指標來著然後再看 \(minr\)。有區別的是向右擴充套件時不是無腦擴充套件,而是擴充套件到剛好夠左邊能跨過下一個最大值。那麼只需每次向右擴充套件之後再二分一下,嘗試把 \(r\) 往回減一些。這個不需要 ST 表。

來看一下時間複雜度。每次當前區間總和因為跨過了一個最大值,所以至少增加了一倍,那麼也就最多增加 \(\log V\) 次。再加上二分和列舉 \(i\) 的複雜度,總複雜度 \(\mathcal{O}(n\log n\log V)\)

細節說多不多說少不少。

#include<bits/stdc++.h>
#define int long long
#define FOR(i,a,b) for(int i=(a);i<=(b);++i)
using namespace std;
const int N=2e5+5;
const int inf=0x3f3f3f3f;
inline int read();
int n,m,k;int a[N];
int f[30][N],g[30][N];
int ans[N],minr[N],maxr[N],s[N];
int getmax(int x,int y){
	int s=__lg(y-x+1);
	return max(f[s][x],f[s][y-(1<<s)+1]);
}
int getmin(int x,int y){
	int s=__lg(y-x+1);
	return min(g[s][x],g[s][y-(1<<s)+1]);
}
void work(){
	n=read();read();
	FOR(i,1,n)a[i]=read(),s[i]=s[i-1]+a[i],ans[i]=0;
	a[n+1]=0;
	FOR(i,1,n)f[0][i]=a[i]+s[i],g[0][i]=s[i]-a[i+1];
	for(int j=1;1<<j<=n;++j)
		for(int i=1;i+(1<<j)-1<=n;++i){
			f[j][i]=max(f[j-1][i],f[j-1][i+(1<<j-1)]);
			g[j][i]=min(g[j-1][i],g[j-1][i+(1<<j-1)]);
		}
	int L,R,l,r,mid;
	FOR(i,1,n){
		l=i,r=i;
		while(1){
			bool flag=0;
			L=1,R=l;
			while(L<R){
				mid=L+R>>1;
				if(s[r]<getmax(mid,R-1))L=mid+1;
				else R=mid;
			}
			if(l!=L)l=L,flag=1;
			if(l==1||r==n)break;
			L=r,R=n;
			while(L<R){
				mid=L+R>>1;
				if(getmin(L,mid)<s[l-1])R=mid;
				else L=mid+1;
			}
			if(r!=L){
				flag=1;
				int x=L;
				L=r+1,R=x;
				while(L<R){
					mid=L+R>>1;
					if(s[mid]-s[l-1]>=a[l-1])R=mid;
					else L=mid+1;
				}
				r=R;
			}
			if(!flag)break;
		}
		if(l==1)minr[i]=r;
		else minr[i]=inf;
		l=i,r=i;
		while(1){
			bool flag=0;
			L=r,R=n;
			while(L<R){
				mid=L+R>>1;
				if(getmin(L,mid)<s[l-1])R=mid;
				else L=mid+1;
			}
			if(r!=L)flag=1,r=R;
			L=1,R=l;
			while(L<R){
				mid=L+R>>1;
				if(s[r]<getmax(mid,R-1))L=mid+1;
				else R=mid;
			}
			if(l!=L)l=L,flag=1;
			if(!flag)break;
		}
		if(l==1)maxr[i]=r;
		else maxr[i]=inf;
	}
	FOR(i,1,n)
		if(maxr[i]!=inf&&minr[i]<=maxr[i])
			ans[minr[i]]++,ans[maxr[i]+1]--;
	FOR(i,1,n)ans[i]+=ans[i-1];
	FOR(i,1,n)printf("%lld ",ans[i]);puts("");
}
signed main()
{
	int T=read();
	while(T--)work();
	return 0;
}
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
	return f*x;
}

相關文章