[AGC009C] Division into Two

一、题目

点此看题

二、解法

首先有一个 \(\tt naive\) 的简单 \(dp\),设 \(f[i]\) 表示最后一个 \(A\) 集合选取的数是 \(a_i\),合法的集合划分方案数,转移可以枚举上一个选取的 \(j\),那么限制是:

  • \(a_i-a_j\geq A\)
  • \(\forall k\in[j+1,i-1),a_{k+1}-a_k\geq B\)

但是发现限制显然是不够的,因为可能有 BAB....A 这种情况,那么被转移点 \(j\) 分开的两个 \(B\) 集合的限制就没有考虑到,但是限于 \(dp\) 我们难以处理这个限制。

这个点睛之笔我已经快要想到了,其实就是弱化这个限制直到我们不需要考虑它也可以合法,我们可以通过交换保证 \(A>B\),然后发现有解的必要条件是 \(\forall i,a_{i+1}-a_{i-1}\geq B\),我们可以先判断有无解然后这个限制就自动合法了。

这两个限制可以分别维护两个指针,那么合法 \(j\) 的范围就是一段区间,直接用前缀和优化即可。

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 100005;
const int MOD = 1e9+7;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,A,B,ans,a[M],f[M],s[M];
signed main()
{
	n=read();A=read();B=read();
	if(A<B) swap(A,B);
	for(int i=1;i<=n;i++)
		a[i]=read();
	for(int i=3;i<=n;i++) if(a[i]-a[i-2]<B)
	{
		puts("0");
		return 0;
	}
	f[0]=s[0]=1;
	for(int i=1,p=0,q=0;i<=n;i++)
	{
		while(q<i && a[i]-a[q+1]>=A) q++;
		if(p<=q) f[i]=(s[q]-(p?s[p-1]:0))%MOD;
		s[i]=(s[i-1]+f[i])%MOD;
		if(i>1 && a[i]-a[i-1]<B) p=i-1;
	}
	for(int i=n;i>=0;i--)
	{
		ans=(ans+f[i])%MOD;
		if(i<n && a[i+1]-a[i]<B) break;
	}
	printf("%lld\n",(ans+MOD)%MOD);
}
原文地址:https://www.cnblogs.com/C202044zxy/p/15564013.html