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)\)

对于代码而言:

  1. 这道题对于后缀的处理还是很有讲究的。代码最后一个 if 语句:由于到下一轮循环才用到(\(i\) 增加 \(1\)),所以等价于 \(i\) 的时候知道了 \(s[i-1]-s[i-2]<b\) 推出只能取到 \(i-2\)(因为以 \(i-2\) 转移,那么 \(i-1\) 还是可以到的,因为 \([i-1,i-1]\) 区间内只有一个数,一定满足要求) 。开头时无需判断的。
  2. 最有统计答案时,需要累加多个 f[i],遇到 \(s_{i+1}-s_i<B\) 则停止。原因是这样相当于后面的数都丢入 \(B\) 集合中,如果不统计答案是不全面的。而遇到前文的条件时,\(i\) 还是可以累加的,理由和 1 中括号前半句话的道理是一样的。但是后面就不行了,其实总的说 1 点理解了这点其实更好理解些。
  3. 代码中使用了边做边统计的方式,可以考虑分开处理的代码。

对于第 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;
}
posted @ 2023-06-01 15:53  wscqwq  阅读(8)  评论(0编辑  收藏  举报