AT_agc009_c
Division into Two
思路
一道 DP 好题。
我们假定 \(A\ge B\)(\(A\) 对应 \(X\) 集合,\(B\) 对应 \(Y\) 集合)。我们先判掉存在 \(3\) 个数两两之差小于 \(B\) 的情况(根据鸽巢原理,三数二集合,这三者必定有二者分在一起)。
DP 状态不难想到:令 \(f_i\) 表示前 \(i\) 个数都划分完成,其中第 \(i\) 个数是分在 \(X\) 的方案数。
首先我们需要枚举一个 \(j\),那么 \([j+1,i-1]\) 这一段都放在 \(Y\) 集合中。然后我们分析得到合法的 \(j\) 需要满足的要求:
- \(j<i\)
- \(s_i-s_j\ge A\)(由于 \(s_i\) 递增,所以满足条件的对会越来越多,所以满足此条件的 \(j\) 是从一开始的前缀)
- 对于 \([j+1,i-1]\) 中的每一个数都满足两两之差 \(\ge B\)。(由于区间 \([j,i-1]\) 和 \([j+1,i-1]\),如果后者不合法则前者一定不合法,那么发现是一个 \(i-1\) 到开头的后缀)。
那有的同学就会想,为什么新加入 \(Y\) 的一段就一定合法呢?也可能不合法呀!
注意我们刚开始判断掉的情况,假设上次 \(Y\) 集合中最后的数是 \(a_k\),由于 \(a_{j+1}-a_k<B\),又有 \(a_k<a_j<a_{j+1}\),那么这三者两两作差一定 \(<B\)(因为最大的和最小的之差已经 \(<B\),那么相邻的就一定更小),所以已经被我们判掉了,不需要担心正确性。
可转移的便是二者的交集。二者都可以用双指针一类的方式维护,然后作前缀和即可快速求得,复杂度为 \(O(1)\)。
对于代码而言:
- 这道题对于后缀的处理还是很有讲究的。代码最后一个
if
语句:由于到下一轮循环才用到(\(i\) 增加 \(1\)),所以等价于 \(i\) 的时候知道了 \(s[i-1]-s[i-2]<b\) 推出只能取到 \(i-2\)(因为以 \(i-2\) 转移,那么 \(i-1\) 还是可以到的,因为 \([i-1,i-1]\) 区间内只有一个数,一定满足要求) 。开头时无需判断的。 - 最有统计答案时,需要累加多个
f[i]
,遇到 \(s_{i+1}-s_i<B\) 则停止。原因是这样相当于后面的数都丢入 \(B\) 集合中,如果不统计答案是不全面的。而遇到前文的条件时,\(i\) 还是可以累加的,理由和 1 中括号前半句话的道理是一样的。但是后面就不行了,其实总的说 1 点理解了这点其实更好理解些。 - 代码中使用了边做边统计的方式,可以考虑分开处理的代码。
对于第 2 点,我认为比较难理解,在此特意点出。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
#define Ls(i,l,r) for(int i=l;i<r;++i)
#define Rs(i,l,r) for(int i=l;i>r;--i)
#define Le(i,l,r) for(int i=l;i<=r;++i)
#define Re(i,l,r) for(int i=l;i>=r;--i)
#define L(i,l) for(int i=0;i<l;++i)
#define E(i,l) for(int i=1;i<=l;++i)
#define W(t) while(t--)
const int N=100010,mod=1e9+7;
typedef long long ll;
int n;
ll a,b,s[N],sum[N];
int main(){
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
// ios::sync_with_stdio(0);
// cin.tie(0);
// cout.tie(0);
#endif
// Insert Code Here
scanf("%d%lld%lld",&n,&a,&b);
if(a<b)swap(a,b);
E(i, n)scanf("%lld",s+i);
E(i, n-2)if(s[i+2]-s[i]<b)return puts("0"),0;
sum[0]=1;
int r=0,l=0;
E(i, n){
while(r<i&&s[i]-s[r+1]>=a)++r;
ll f=0;
if(l<=r)f=(sum[r]-(r?sum[l-1]:0)+mod)%mod;
sum[i]=(sum[i-1]+f)%mod;
if(i>1&&s[i]-s[i-1]<b)l=i-1;
}
ll ans=0;
Re(i, n, 0){
(ans+=(sum[i]-sum[i-1]+mod)%mod)%=mod;
if(i<n&&s[i+1]-s[i]<b)break;
}
printf("%lld",ans);
return 0;
}