[AGC009C]Division into 2

针对一个长度为N的递增序列,将其分为两组X和Y,使X中元素差不小于A,Y中元素差不小于B。通过动态规划算法解决方案数问题,并采用线段树和懒惰标记进行优化。

题意:

有一个长度为$N$的递增序列$S_i$,要把它分成$X,Y$两组,使得$X$中元素两两之差不小于$A$且$Y$中元素两两之差不小于$B$,求方案数

首先考虑$O\left(n^2\right)$的做法:

为了方便,我们令$S_0=-\infty$

设$f_{M,i,j}(M\in\{X,Y\},1\leq i\leq n,0\leq j\lt i)$表示已划分好$S_{1\cdots i}$且$S_j$是最后一个不属于$M$的元素的方案数

已算好$f_{X,1\cdots i,j}$和$f_{Y,1\cdots i,j}$,如何转移?

①若$S_{i+1}-S_i\geq A$,$S_{i+1}$可被放入$X$中,则$f_{X,i+1,0\cdots i-1}=f_{X,i,0\cdots i-1}$

否则$S_i,S_{i+1}$不可一起被放入$X$中,$f_{X,i+1,0\cdots i-1}=0$

②显然$f_{Y,i+1,i}=\sum\limits_{j=0}^{i-1}[S_{i+1}-S_j\geq B]f_{X,i,j}$

对$f_Y$的处理相似

最后的答案就是$\sum\limits_{i=0}^{n-1}f_{X,n,i}+\sum\limits_{i=0}^{n-1}f_{Y,n,i}$

#include<stdio.h>
#define ll long long
#define mod 1000000007
int fx[2010][2010],fy[2010][2010];
ll a[2010];
int main(){
	int n,i,j;
	ll A,B;
	scanf("%d%lld%lld",&n,&A,&B);
	for(i=1;i<=n;i++)scanf("%lld",a+i);
	a[0]=-4223372036854775807ll;
	fx[1][0]=fy[1][0]=1;
	for(i=1;i<n;i++){
		if(a[i+1]-a[i]>=A){
			for(j=0;j<i;j++)fx[i+1][j]=fx[i][j];
		}
		if(a[i+1]-a[i]>=B){
			for(j=0;j<i;j++)fy[i+1][j]=fy[i][j];
		}
		for(j=0;j<i;j++){
			if(a[i+1]-a[j]>=B)fy[i+1][i]=(fy[i+1][i]+fx[i][j])%mod;
			if(a[i+1]-a[j]>=A)fx[i+1][i]=(fx[i+1][i]+fy[i][j])%mod;
		}
	}
	j=0;
	for(i=0;i<n;i++)j=((j+fx[n][i])%mod+fy[n][i])%mod;
	printf("%d",j);
}

考虑优化~

首先我们肯定不能开二维数组,考虑当前DP到$S_i$,只存$f_{M,j}$,并看一看当$i$变为$i+1$对答案的影响

因为$S$是递增的,所以满足$S_{i+1}-S_j\geq B$的$S_j$一定是一段前缀,所以我们可以用二分找到右端点并用线段树求区间和

其他转移就相当于线段树的单点更新

再用lazy tag实现清零即可

#include<stdio.h>
#define ll long long
#define mod 1000000007
int sumx[400010],sumy[400010],lazx[400010],lazy[400010],*laz,*sum,n;
ll a[100010];
void pushdown(int x){
	if(laz[x]){
		laz[x<<1]=laz[x<<1|1]=1;
		sum[x<<1]=sum[x<<1|1]=0;
		laz[x]=0;
	}
}
int query(int L,int R,int l,int r,int x){
	if(L<=l&&r<=R)return sum[x];
	pushdown(x);
	int mid=(l+r)>>1,ans=0;
	if(L<=mid)ans=(ans+query(L,R,l,mid,x<<1))%mod;
	if(mid<R)ans=(ans+query(L,R,mid+1,r,x<<1|1))%mod;
	return ans;
}
void modify(int pos,int v,int l,int r,int x){
	if(l==r){
		sum[x]=(sum[x]+v)%mod;
		return;
	}
	pushdown(x);
	int mid=(l+r)>>1;
	if(pos<=mid)
		modify(pos,v,l,mid,x<<1);
	else
		modify(pos,v,mid+1,r,x<<1|1);
	sum[x]=(sum[x<<1]+sum[x<<1|1])%mod;
}
int queryx(int L,int R){
	laz=lazx;
	sum=sumx;
	return query(L,R,0,n-1,1);
}
void modifyx(int pos,int v){
	laz=lazx;
	sum=sumx;
	modify(pos,v,0,n-1,1);
}
int queryy(int L,int R){
	laz=lazy;
	sum=sumy;
	return query(L,R,0,n-1,1);
}
void modifyy(int pos,int v){
	laz=lazy;
	sum=sumy;
	modify(pos,v,0,n-1,1);
}
int main(){
	int i,l,r,mid,x,t1,t2;
	ll A,B;
	scanf("%d%lld%lld",&n,&A,&B);
	for(i=1;i<=n;i++)scanf("%lld",a+i);
	a[0]=-4223372036854775807ll;
	modifyx(0,1);
	modifyy(0,1);
	for(i=1;i<n;i++){
		l=0;
		r=i-1;
		while(l<=r){
			mid=(l+r)>>1;
			if(a[i+1]-a[mid]>=B){
				x=mid;
				l=mid+1;
			}else
				r=mid-1;
		}
		t1=queryx(0,x);
		l=0;
		r=i-1;
		while(l<=r){
			mid=(l+r)>>1;
			if(a[i+1]-a[mid]>=A){
				x=mid;
				l=mid+1;
			}else
				r=mid-1;
		}
		t2=queryy(0,x);
		if(a[i+1]-a[i]<A){
			sumx[1]=0;
			lazx[1]=1;
		}
		if(a[i+1]-a[i]<B){
			sumy[1]=0;
			lazy[1]=1;
		}
		modifyy(i,t1);
		modifyx(i,t2);
	}
	printf("%d",(queryx(0,n-1)+queryy(0,n-1))%mod);
}

转载于:https://www.cnblogs.com/jefflyy/p/7711102.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值