【ybtoj】【二分】攻击法坛

题意

image

题解

数据范围想到复杂度大概 \(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;
}
posted @ 2021-09-29 21:30  conprour  阅读(56)  评论(0编辑  收藏  举报