【ybtoj】【二分】攻击法坛
题意
题解
数据范围想到复杂度大概 \(O(n^2\log n)\) 。
由于复杂度的提示加上单调性比较明显,不难想到二分答案。
一开始大概能想到一种思路:记录 \(dp(k,i,j)\) 表示前 \(k\) 个节点第一根法杖用了 \(i\) 次,第二根法杖用了 \(j\) 次的可行性,但是显然三维空间会爆炸(即使是 \(2000^3\))。
换个思路,不再记录前几个节点,直接记录 \(dp(i,j)\) 表示第一根法杖用了 \(i\) 次,第二根法杖用了 \(j\) 次最多能走到多少个节点。预处理出每一个法坛用第一种和第二种法杖能走到的最远位置,转移式子比较显然,不详细说明。如果实在推不出来就看下代码吧。(其实就是懒得打\(\LaTeX\))
最后还有一个问题,题里 \(R,G\) 都有 \(10^9\) 级别,二维能开下吗?
实际上这就是唬人的,只要法坛数量比法杖的使用次数少,一定可以用 \(n\) 次覆盖,直接输出 \(1\) 即可。
所以空间复杂度 \(O(n^2)\) ,没有任何问题。
总结
- 先考虑出题目的特殊情况,说不定可以简化题目。
- dp 不要只局限于位置作为下标。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int INF = 0x3f3f3f3f,N = 2005;
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,a,b,p[N];
int dp[N][N];
int x[N],y[N];
void pretreat(int len)
{
for(int i=1;i<=n;i++)
{
for(int j=i;j<=n;j++)
if(p[i]+len-1>=p[j]) x[i]=j;
else break;
}
for(int i=1;i<=n;i++)
{
for(int j=i;j<=n;j++)
if(p[i]+2*len-1>=p[j]) y[i]=j;
else break;
}
}
inline bool check(int len)
{
memset(dp,0,sizeof(dp));
x[n+1]=y[n+1]=n;
pretreat(len);
// dp[0][0]=0;
// for(int i=1;i<=n;i++) printf("(%d):x,y(%d,%d)\n",i,x[i],y[i]);
for(int i=0;i<=a;i++)
for(int j=0;j<=b;j++)
{
if(i) dp[i][j]=max(dp[i][j],x[dp[i-1][j]+1]);
if(j) dp[i][j]=max(dp[i][j],y[dp[i][j-1]+1]);
}
// printf("dp[a][b]=%d\n",dp[a][b]);
return dp[a][b]==n;
}
int main()
{
n=read(),a=read(),b=read();
int r=0;
for(int i=1;i<=n;i++) p[i]=read(),r=max(r,p[i]);
sort(p+1,p+n+1);
int l=1;
if(a+b>=n)
{
printf("1\n");
return 0;
}
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
printf("%d",l);
return 0;
}