【二分+DP】【YBTOJ】攻击法坛
题目链接:F. 3.攻击法坛
题意
数轴上有\(n\)个给定的点,\(p\)个长为\(L\)和\(q\)个长为\(2L\)的线段,求出\(L\)的最小值使得所有点被线段覆盖。
解析
很考验思维的一道题.
发现\(L\)的可行性有单调性,于是考虑二分\(L\)。
在check
中:
- 设\(f1[i]\)表示,在当前\(L\)下,从第\(i\)个点开始可覆盖到第\(f1[i]\)个点;
- 设\(f2[i]\)表示,在当前\(2L\)下,从第\(i\)个点开始可覆盖到第\(f2[i]\)个点。
(用\(n^2\)求出)
后续使用dp: - 设\(dp[i][j]\)表示使用\(i\)次第一条线段,\(j\)次第二条线段,便可得:
- \(dp[i][j] = max\{\cdots,f1[_{dp[i-1][j]+1}],f2[_{dp[i][j-1]+1}]\}\)
分别表示从减少一次第一条线段和第二条线段转移而来。
- \(dp[i][j] = max\{\cdots,f1[_{dp[i-1][j]+1}],f2[_{dp[i][j-1]+1}]\}\)
最后,如果达到了第\(n\)个点即返回。
代码
#include <bits/stdc++.h>
#define fo(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout);
using namespace std;
const int INF = 0x3f3f3f3f,N = 2e3+5;
typedef long long ll;
typedef unsigned long long ull;
inline ll read(){
ll ret = 0 ;char ch = ' ' , c = getchar();
while(!(c >= '0' && c <= '9'))ch = c , c = getchar();
while(c >= '0' && c <= '9')ret = (ret << 1) + (ret << 3) + c - '0' , c = getchar();
return ch == '-' ? -ret : ret;
}
int n,p,q;
int a[N];
int l,r;
int f1[N],f2[N],dp[N][N];
inline bool check(int x){
// printf("check(%d)\n",x);
memset(f1,0,sizeof(f1)),memset(f2,0,sizeof(f2)),memset(dp,0,sizeof(dp));
for(int i = 1 ; i <= n ; i ++)
for(int j = i ; j <= n ; j ++){
if(1LL * a[i] + x > 1LL * a[j])
f1[i] = j;
if(1LL * a[i] + x + x > 1LL * a[j])
f2[i] = j;
}
for(int i = 0 ; i <= p ; i ++)
for(int j = 0 ; j <= q ; j ++){
if(i)
dp[i][j] = max(dp[i][j],f1[dp[i-1][j]+1]);
if(j)
dp[i][j] = max(dp[i][j],f2[dp[i][j-1]+1]);
if(dp[i][j] >= n)return 1;
}
return dp[p][q] >= n;
}
signed main(){
n = read() , p = read() , q = read();
for(int i = 1 ; i <= n ; i ++)
a[i] = read();
sort(a+1,a+n+1);
l = 1 , r = a[n]-a[1]+1;
while(l < r){
int mid = (l + r) >> 1;
if(check(mid))r = mid;
else l = mid + 1;
}
printf("%d",l);
return 0;
}