题意
给定数组 a a a,求分成 A , B A,B A,B集合的方案数。
A A A集合任意两个元素差值大于等于 x x x, B B B集合任意两个元素插值大于等于 y y y
先把 a a a数组排序,考虑状态设计
显然需要知道当前 A , B A,B A,B集合最大的元素是谁
一个单纯的想法是 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示考虑到第 i i i个数, A A A集合最大是 a j a_j aj, B B B集合最大是 b k b_k bk
发现 j , k j,k j,k必定有一个是 i i i,所以我们的状态只需要保存另一个索引即可
f [ i ] [ j ] [ 0 / 1 ] f[i][j][0/1] f[i][j][0/1]表示当前第 A / B A/B A/B集合最大的是 a i a_i ai, B / A B/A B/A集合最大的是 a j a_j aj
为了方便观察,我们令 f a [ i ] [ j ] fa[i][j] fa[i][j]表示 A A A集合最大是 a i a_i ai, f b [ i ] [ j ] fb[i][j] fb[i][j]表示 B B B集合最大是 a i a_i ai
如果上一个元素是属于 A A A集合的,那么
f a [ i ] [ j ] + = f a [ i − 1 ] [ j ] ( j ∈ [ 0 , i − 2 ] & & a i − a i − 1 > = x ) fa[i][j]+=fa[i-1][j]\ \ (j\in[0,i-2]\&\&a_i-a_{i-1}>=x) fa[i][j]+=fa[i−1][j] (j∈[0,i−2]&&ai−ai−1>=x)
如果上一个元素是属于 B B B集合的,那么
f a [ i ] [ i − 1 ] + = ∑ j ∈ [ 0 , i − 2 ] & & a i − a j > = x f b [ i − 1 ] [ j ] fa[i][i-1]+=\sum\limits_{j\in[0,i-2]\&\&a_i-a_j>=x}fb[i-1][j] fa[i][i−1]+=j∈[0,i−2]&&ai−aj>=x∑fb[i−1][j]
对于 f b fb fb的转移同理
考虑把 f a , f b fa,fb fa,fb放在线段树上转移。
观察转移方程一,若当前 a i − a i − 1 < x a_i-a_{i-1}<x ai−ai−1<x只需要把 [ 0 , i − 2 ] [0,i-2] [0,i−2]位置置零即可,否则不用改变
观察转移方程二,我们可以二分出一个满足条件的最大 j j j,这样就是一个区间求和
都可以用线段树维护,整体复杂度 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))
然而,发现操作一的零一定是从 0 , 1 , 2 , 3... 0,1,2,3... 0,1,2,3...这样顺序赋值过来的
操作二满足条件的最大 j j j一定是单调递增的
那么其实就是个小模拟,可以在 O ( n ) O(n) O(n)的时间内转移
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 1e6+10;
const int mod = 1e9+7;
int n,x,y,a[maxn],fa[maxn],fb[maxn];
int azero,bzero,prea[maxn],preb[maxn];
int ida,idb;
int get(int l,int r,int pre[])
{
if( r<l ) return 0ll;
if( l==0 ) return pre[r];
return ( pre[r]-pre[l-1] )%mod;
}
signed main()
{
scanf("%lld%lld%lld",&n,&x,&y);
for(int i=1;i<=n;i++) scanf("%lld",&a[i] );
sort( a+1,a+1+n );
fa[0] = fb[0] = prea[0] = preb[0] = 1;
for(int i=2;i<=n;i++)
{
while( ida+1<=i-2&&a[i]-a[ida+1]>=x ) ida++;//目前可转移的最大的索引,可以从[0,ida]转移而来
while( idb+1<=i-2&&a[i]-a[idb+1]>=y ) idb++;
fa[i-1] = get( bzero,ida,preb );
fb[i-1] = get( azero,idb,prea );
prea[i-1] = ( prea[i-2]+fa[i-1] )%mod;
preb[i-1] = ( preb[i-2]+fb[i-1] )%mod;
if( a[i]-a[i-1]<x )
{
for(int j=azero;j<=i-2;j++) fa[j] = 0;
azero = i-1;
}
if( a[i]-a[i-1]<y )
{
for(int j=bzero;j<=i-2;j++) fb[j] = 0;
bzero = i-1;
}
}
int ans = 0;
for(int i=0;i<=n;i++)
ans = ( ans+fa[i]+fb[i] )%mod;
cout << ( ans%mod+mod )%mod;
}
线段树慢一点, O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))
#include <bits/stdc++.h>
using namespace std;
#define mid (l+r>>1)
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson ls,l,mid
#define rson rs,mid+1,r
#define int long long
const int maxn = 1e6+10;
const int mod = 1e9+7;
int n,x,y,a[maxn];
int azero,bzero,ida,idb;
struct Line_Tree
{
int sum[maxn];
void insert(int rt,int l,int r,int id,int val)
{
if( r<id || l>id ) return;
if( l==r && l==id ){ sum[rt] = val; return; }
insert(lson,id,val); insert(rson,id,val);
sum[rt] = ( sum[ls]+sum[rs] )%mod;
}
int ask(int rt,int l,int r,int L,int R)
{
if( l>R || r<L ) return 0ll;
if( l>=L&&r<=R ) return sum[rt];
return ( ask(lson,L,R)+ask(rson,L,R) )%mod;
}
}t1,t2;
signed main()
{
scanf("%lld%lld%lld",&n,&x,&y);
for(int i=1;i<=n;i++) scanf("%lld",&a[i] );
sort( a+1,a+1+n );
t1.insert(1,0,n,0,1); t2.insert(1,0,n,0,1);
for(int i=2;i<=n;i++)
{
while( ida+1<=i-2&&a[i]-a[ida+1]>=x ) ida++;//目前可转移的最大的索引,可以从[0,ida]转移而来
while( idb+1<=i-2&&a[i]-a[idb+1]>=y ) idb++;
int tempa = 0, tempb = 0;
if( ida>=bzero ) tempa = t2.ask( 1,0,n,bzero,ida );
if( idb>=azero ) tempb = t1.ask( 1,0,n,azero,idb );
t1.insert(1,0,n,i-1,tempa ); t2.insert(1,0,n,i-1,tempb );
if( a[i]-a[i-1]<x )//当然这里可以线段树直接区间赋值0,不过直接暴力就好了
{
for(int j=azero;j<=i-2;j++) t1.insert(1,0,n,j,0);
azero = i-1;
}
if( a[i]-a[i-1]<y )
{
for(int j=bzero;j<=i-2;j++) t2.insert(1,0,n,j,0);
bzero = i-1;
}
}
cout << ( t1.sum[1]+t2.sum[1] )%mod;
}