[AGC009C Division into Two] [DP好题]

这题真的挺不错的。

首先观察发现:任意两元素相差>=A,只要满足排序后相邻两元素相差>=A,B同理,所以我们考虑从小到大依此决定每个元素应该扔到哪个集合,这样就只要维护两个集合中此时最大的元素即可。得到朴素的动态规划方法:

f[i][j]表示放完了前max(i,j)个元素,且A集合中最大的元素是i,B集合中最大的元素是j,有多少种方案。

转移也很方便,直接枚举下一个元素放到哪个集合即可,注意相邻元素的差值是否合法。

复杂度O(n^2)。

考虑优化。我们记录的信息太详细了,是否可以省掉一维呢?其实仔细想想,对于每个集合来说,我们真正想要知道的并不是最大元素是谁,而是当前元素能否放进去,所以我们考虑省掉其中一维信息(省得太多不好转移,所以只省掉一维比较方便),也就是用这样的新状态来表示:f[i]表示已经放完了前i个元素,第i个元素是在A集合,并且保证s[i+1]可以放进B集合,这时有多少方案;g[i]表示已经放完了前i个元素,第i个元素是在B集合,并且保证s[i+1]可以放进A集合,这时有多少方案。

这样我们的状态就变成线性的了。如何转移呢?

这里我们只讨论f[i]如何向后更新,因为g[i]是完全类似的。

往后更新只有两种情况,一种是将s[i+1]放入A集合:f[i]->f[i+1] (|s[i+1]-s[i]|>=A)

另一种则是将s[i+1]放入B集合,注意到这个时候我们会推到g[i+1],但是并不能保证s[i+2]可以放入A,所以我们应该找到最小的p(方法下面再说),满足s[p+1]可以放入A集合,然后将s[i+1..p]全部扔到B集合里面。 方程:f[i]->g[p]。

这样,我们就完成了O(N)的DP。上面每种方案显然合法,并且没有重复,所以(f[n]+g[n])<=正确答案,又由于每种正确方案都可以在上面的转移中体现出来,因此正确答案<=(f[n]+g[n]),因此(f[n]+g[n])=正确答案,这个DP是不重复、不遗漏的。

 

补充:如何求上述的p。我们用线性的方法预处理出四个数组,la,ra,lb,rb。

la[i]表示从i向后找到的第一个位置,满足| s[ la[i] ] - s[i] |>=A,ra[i]表示从i向后找到的第一个位置,满足| s[ ra[i]+1 ] - s[ ra[i] ] |<A

B数组同理。

显然p<=rb[i+1],因为超过这个位置后的元素就不能继续扔到B集合了,并且p>=la[i]-1,否则s[p+1]不能扔到A集合中。

因此如果rb[i+1]<la[i]-1,那么无法找到合法的位置p,就不需要转移了。否则令p=max(i+1,la[i]-1),f[i]->g[p]即可。

 

[总结]

    这题把握住了真正有用的信息,从而简化了状态,并且为了避免重复转移,选择了最近的一个合法位置来转移。这些思路都是很好的。

#include <cstdio>
#include <algorithm>
#define ll long long
#define rep(i,j,k) for (i=j;i<=k;i++)
#define down(i,j,k) for (i=j;i>=k;i--)
using namespace std;
const int N=1e5+5,INF=1e9,mod=1e9+7;
int n,i,j,ra[N],rb[N],la[N],lb[N];
ll A,B,s[N];
int f[N],g[N];
void updata(int &x,int y) { x+=y; if (x>=mod) x-=mod; }
int main()
{
	//freopen("division.in","r",stdin);
	//freopen("division.out","w",stdout);
	scanf("%d%lld%lld",&n,&A,&B);
	rep(i,1,n) scanf("%lld",&s[i]);
	s[n+1]=INF; ra[n]=rb[n]=ra[n+1]=rb[n+1]=n+1;
	down(i,n-1,1)
	{
		if (abs(s[i+1]-s[i])<A) ra[i]=i; else ra[i]=ra[i+1];
		if (abs(s[i+1]-s[i])<B) rb[i]=i; else rb[i]=rb[i+1];
	}
	rep(i,1,n)
	{
		la[i]=max(la[i-1],i+1);
		while (la[i]<=n && abs(s[la[i]]-s[i])<A) la[i]++;
		lb[i]=max(lb[i-1],i+1);
		while (lb[i]<=n && abs(s[lb[i]]-s[i])<B) lb[i]++;
	}
	f[1]=g[1]=1;
	rep(i,1,n-1)
	{
		if (abs(s[i+1]-s[i])>=A) updata(f[i+1],f[i]);
		if (rb[i+1]>=la[i]-1) updata(g[max(i+1,la[i]-1)],f[i]);
		if (abs(s[i+1]-s[i])>=B) updata(g[i+1],g[i]);
		if (ra[i+1]>=lb[i]-1) updata(f[max(i+1,lb[i]-1)],g[i]);
	}
	printf("%d\n",(f[n]+g[n])%mod);
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值