洛谷P3957跳房子
题目链接
这是一道很有(du )趣(liu )的题目
题目思路
二分+深搜
我们可以发现分数是根据灵活性的上升而上升的,也就是说分数对于灵活性而言是有序的。
那么我们就完全没有必要去一个个枚举分数,而是可以用二分来加快速度。
然后我们用深搜dfs非常暴力地计算最大分数,并与k比较,就可以获得TLE20分
代码如下:
#include<iostream>
#define MAXN 500005
#define INF 0x3f3f3f3f
using namespace std;
int d,n,k,minl,maxl,ans;
struct node{
int x,s;
}a[MAXN];
void dfs(int pos,int sum){
ans=max(ans,sum);
for(int i=pos+1;i<=n;i++){
if(a[i].x-a[pos].x>maxl) break;
if(a[i].x-a[pos].x<minl) continue;
dfs(i,sum+a[i].s);
}
}
bool check(int mid){
minl=max(1,d-mid);
maxl=d+mid;
ans=-INF;
dfs(0,0);
return ans>=k;
}
int main(){
cin>>n>>d>>k;
for(int i=1;i<=n;i++){
cin>>a[i].x>>a[i].s;
}
int l=0,r=INF;
while(l<r){
int mid=(l+r)/2;
if(check(mid)) r=mid;
else l=mid+1;
}
if(check(l)) cout<<l;
else cout<<-1;
return 0;
}
二分+动态规划
那么我们就需要思考加速的方法。很明显二分已经足够快了,所以导致我们TLE的罪魁祸首就是深搜。
那我们check函数里用什么呢? 当然是动态规划啦!
我们可以发现深搜中的前后关系是线性的,所以可以用动态规划。
只要中间过程大于等于k即可,因为后面的值一定越来越大。这样做,你可以获得TLE50分。
代码如下:
#include<iostream>
#define MAXN 500005
#define INF 0x3f3f3f3f
using namespace std;
int d,n,k,minl,maxl,ans;
struct node{
int x,s;
}a[MAXN];
int f[MAXN];
bool check(int mid){
minl=max(1,d-mid);
maxl=d+mid;
for(int i=1;i<=n;i++){
f[i]=-INF;
for(int j=0;j<i;j++){
int dis=a[i].x-a[j].x;
if(dis<minl) break;
if(dis>maxl) continue;
f[i]=max(f[i],f[j]+a[i].s);
}
if(f[i]>=k) return true;
}
return false;
}
int main(){
cin>>n>>d>>k;
for(int i=1;i<=n;i++){
cin>>a[i].x>>a[i].s;
}
int l=0,r=INF;
while(l<r){
int mid=(l+r)/2;
if(check(mid)) r=mid;
else l=mid+1;
}
if(check(l)) cout<<l;
else cout<<-1;
return 0;
}
二分+动态规划+玄学
我们已经用动态规划获得了50分,但还是挂了。
怎么办?俗话说:“世上无难题,只要肯放弃。”
动规挂了,我们就优化动规
在一波玄学优化后……
#include<iostream>
#define MAXN 500005
#define INF 0x3f3f3f3f
using namespace std;
int d,n,k,minl,maxl,ans;
struct node{
int x,s;
}a[MAXN];
int f[MAXN];
bool check(int mid){
minl=max(1,d-mid);
maxl=d+mid;
for(int i=1;i<=n;i++){
f[i]=-INF;
for(int j=i-1;j>=0;j--){
int dis=a[i].x-a[j].x;
if(dis<minl) continue;
if(dis>maxl) break;
f[i]=max(f[i],f[j]+a[i].s);
}
if(f[i]>=k) return true;
}
return false;
}
int main(){
cin>>n>>d>>k;
for(int i=1;i<=n;i++){
cin>>a[i].x>>a[i].s;
}
int l=0,r=INF;
while(l<r){
int mid=(l+r)/2;
if(check(mid)) r=mid;
else l=mid+1;
}
if(check(l)) cout<<l;
else cout<<-1;
return 0;
}
总之就是很玄学。。。
二分+动态规划+单调队列优化
我们发现,在check函数中我们使用了双层循环,其实是用不着的。
第二重循环中,我们遍历了很多无效的项,这时候,我们就可以通过单调队列优化来解决。
因为如果 a [ i ] . x − a [ j ] . x > m a x l a[i].x-a[j].x>maxl a[i].x−a[j].x>maxl的话, a [ i + 1 ] . x − a [ j ] . x > m a x l a[i+1].x-a[j].x>maxl a[i+1].x−a[j].x>maxl,这样 j j j这个点后面就彻底没用了。
所以这有用吗? 要不还是洗洗睡吧。
当然有用!
我们就可以弄个队列,对于点i,有用的时候就把它留在队列里,没用就把它踢出队列,扔进垃圾桶
这样, i i i这个点我们后面就不需要无效遍历了
具体代码如下
#include<iostream>
#define MAXN 500005
#define INF 0x3f3f3f3f
using namespace std;
int d,n,k,minl,maxl,ans,h,t;
struct node{
int x,s;
}a[MAXN];
int f[MAXN],q[MAXN];
void push(int i){
while(t>h&&f[q[t-1]]<f[i])
t--;
q[t]=i;
t++;
}
bool check(int mid){
minl=max(1,d-mid);
maxl=d+mid;
int j=0;
h=t=0;
for(int i=1;i<=n;i++){
f[i]=-INF;
while(j<i&&a[i].x-a[j].x>=minl) push(j++);
while(h<t&&a[i].x-a[q[h]].x>maxl) h++;
if(t==h||f[q[h]]==-INF) continue;
else f[i]=f[q[h]]+a[i].s;
if(f[i]>=k) return true;
}
return false;
}
int main(){
cin>>n>>d>>k;
for(int i=1;i<=n;i++){
cin>>a[i].x>>a[i].s;
}
int l=0,r=INF;
while(l<r){
int mid=(l+r)/2;
if(check(mid)) r=mid;
else l=mid+1;
}
if(check(l)) cout<<l;
else cout<<-1;
return 0;
}
完结撒花
这是我之前写在博客园上面的,现在放到CSDN博客上面来。
写在最后
感谢各位花了这么长的时间来看一个初二牲蒟蒻的博客。
我的博客