【AtCoder Grand Contest 009 C】Division into Two
动态规划。
题目大意:
给定 $n,A,B$ 和 $n$ 个整数 $s_1,s_2,s_3,\ldots,s_n$,要求把这 $n$ 个数划分为两个集合,满足第一个集合中的元素两两之差大于等于 $A$,第二个集合中的元素两两之差大于等于 $B$。
问有多少种不同的划分方法。
题解:
我们把 $s$ 从小到大排序,依次考虑。并且我们钦定 $A\leq B$。
首先判无解。如果存在 $i\in(1,n)$,有 $s_{i+1}-s_{i-1}\lt A$,那么无解($s_{i-1},s_{i},s_{i+1}$ 不管怎么放都不能满足条件)。
然后考虑 dp。令 $f_{i,j}$ 表示前 $i$ 个数,最后一个放在第二个集合(限制条件为 $B$ 的那个)的数的位置为 $j$ 的方案数。
有以下情况:
- 数 $s_i$ 放在第一个集合(限制条件为 $A$ 的那个),且 $s_{i}-s_{i-1}\geq A$。则 $f_{i,j}=f_{i-1,j}$($0\leq j\lt i$)。
- 数 $s_i$ 放在第一个集合(限制条件为 $A$ 的那个),但 $s_{i}-s_{i-1}\lt A$。则 $f_{i,j}=0$($0\leq j\lt i-1$),$f_{i,i-1}=f_{i-1,i-1}$。
- 数 $s_i$ 放在第二个集合。则 $f_{i,i}=\sum_{s_i-s_j\geq B}f_{i-1,j}$。
可以直接用前缀和优化这个 dp。
时间复杂度 $O(n)$。
Code:
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=1e5+5,md=1e9+7;
LL A,B,s[N];
int n,h,dp[N];
inline void upd(int&a){a+=a>>31&md;}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>A>>B;
if(A>B)swap(A,B);
for(int i=1;i<=n;++i)cin>>s[i];
s[0]=-B;
sort(s+1,s+n+1);
for(int i=2;i<n;++i)
if(s[i+1]-s[i-1]<A){
cout.put('0');
return 0;
}
h=0,dp[0]=1;
for(int i=1,it=0;i<=n;++i){
dp[i]=dp[i-1];
while(s[it+1]+B<=s[i])++it;
if(it>=h){
upd(dp[i]+=dp[it]-md);
if(h)upd(dp[i]-=dp[h-1]);
}
if(s[i]-s[i-1]<A)h=i-1;
}
upd(dp[n]-=dp[h-1]);
cout<<dp[n]<<'\n';
return 0;
}