P1941 飞扬的小鸟[NOIP2014提高组]

前50分显然是非常水的。甚至能用搜索过。

#include<bits/stdc++.h>
using namespace std;

#define pb push_back
#define fi first
#define se second
#define ll long long
#define pq priority_queue
#define mp make_pair
#define pii pair<int,int>
#define mod 998244353
#define debug(x) cerr<<#x<<"="<<x<<'\n'

int lowbit(int x) {return x&(-x);}

struct a {
    int i,j,f,cnt;
};

a build(int aa,int b,int c,int d) {
    a tmp;
    tmp.i=aa,tmp.j=b,tmp.f=c,tmp.cnt=d;
    return tmp;
}

const int maxn=22,maxm=19;

deque <a> q;
int n,m,k;
int l[maxn],r[maxn];
int x[maxn],y[maxn];
bool pipe[maxn];

int main(){
    scanf("%d%d%d",&n,&m,&k);
    for (int i=0;i<n;i++) {
        scanf("%d%d",&x[i],&y[i]);
        l[i]=0,r[i]=m+1;
    }
    for (int i=0;i<k;i++) {
        int p;scanf("%d",&p);
        pipe[p]=true;
        scanf("%d%d",&l[p],&r[p]);
    }
    int ans=0,ans2=1e9;
    for (int i=1;i<=m;i++) q.push_back(build(0,i,0,0));
    while (!q.empty()) {
        int i=q.front().i,j=q.front().j,f=q.front().f,cnt=q.front().cnt;
        q.pop_front();
        if (i==n) {
            ans2=min(f,ans2);
            continue;
        }
        if (j<=l[i]||j>=r[i]) continue;
        if (pipe[i]) cnt++;
        ans=max(ans,cnt);
        if (f>=ans2) continue;
        if (j-y[i]>0) q.push_front(build(i+1,j-y[i],f,cnt));
        q.push_back(build(i+1,min(m,j+x[i]),f+1,cnt));
        if (j+x[i]>=m) continue;
        q.push_back(build(i+1,min(m,j+2*x[i]),f+2,cnt));
        if (j+2*x[i]>=m) continue;
        q.push_back(build(i+1,min(m,j+3*x[i]),f+3,cnt));
    }
    if (ans2==1e9) cout<<0<<endl<<ans<<endl;
    else cout<<1<<endl<<ans2<<endl; 
    return 0;
}

这道题显然就是一道dp。状态也非常显然:

f[i][j]表示考虑到横坐标为i的位置高度为j的最小跳跃次数。转移分两种情况:

  • 不跳,直接从前一个位置掉下来:f[i][j]=f[i-1][j+y[i-1]]
  • 跳:枚举所有比j低且与j高度差为x[i-1] k倍(k为正整数)的高度h:f[i][j]=min(f[i][j],f[i-1][h]+k)
  • 特别注意当j=m时需要特判

显然,第一种情况的转移是O(1)的,但第二种情况转移为O(m),那么总的复杂度为O(nm^2),只能得到70分。因此我们需要对第二种情况的转移进行优化。令g[i][j]为第二种情况的最小花费。

对于每一步转移时取min的值分为两个部分:f数组和k。可以发现g[i][j]在转移时用到的f数组只比g[i][j-x[i-1]]多一个f[i-1][j]+1,其他部分完全相同,而对于转移时用到的每一个相同的f数组,g[i][j]在转移时后面对应的k比g[i][j-x[i-1]]多1。因此

g[i][j]=min(g[i][j-x[i-1]+1,f[i-1][k]+1)

这样我们就将转移的复杂度降到了O(1)。

还是一定要注意j=m是的特判。

代码如下:

#include<bits/stdc++.h>
using namespace std;

#define pb push_back
#define fi first
#define se second
#define ll long long
#define pq priority_queue
#define mp make_pair
#define pii pair<int,int>
#define mod 998244353
#define debug(x) cerr<<#x<<"="<<x<<'\n'

int lowbit(int x) {return x&(-x);}

int n,m,k;

const int maxn=1e4+10,maxm=1e3+10;
const int INF=1e9+11;

int x[maxn],y[maxn];
int f[maxn][maxm];
int g[maxm];
int l[maxn],r[maxn];
bool pipe[maxn];

int main(){
    scanf("%d%d%d",&n,&m,&k);
    for (int i=0;i<=n;i++) l[i]=0,r[i]=m+1;
    for (int i=0;i<n;i++) scanf("%d%d",&x[i],&y[i]);
    for (int i=0;i<k;i++) {
        int p;
        scanf("%d",&p);
        pipe[p]=true;
        scanf("%d%d",&l[p],&r[p]);
    }
    int cnt=0,ans=INF;
    for (int i=0;i<=n;i++) {
        for (int j=0;j<=m;j++) f[i][j]=(i==0?0:INF);
        bool ok=false;
        if (i) {
            for (int j=1;j<=m;j++) {
                if (j-x[i-1]>0) g[j]=min(f[i-1][j-x[i-1]],g[j-x[i-1]])+1;
                else g[j]=INF;
                if (j==m) {
                    for (int k=j;k>=max(0,j-x[i-1]);k--) {
                        g[j]=min(g[j],min(g[k],f[i-1][k])+1);
                    }
                }
            }
            for (int j=l[i]+1;j<r[i];j++) {
                if (j+y[i-1]<=m) f[i][j]=f[i-1][j+y[i-1]];
                f[i][j]=min(f[i][j],g[j]);
                if (f[i][j]!=INF) ok=true;
                if (i==n) ans=min(ans,f[i][j]);
            }
        }
        if (!ok&&i>0) {
            break;
        }
        else if (pipe[i]) cnt++;
    }
    if (ans==INF) cout<<0<<endl<<cnt<<endl;
    else cout<<1<<endl<<ans<<endl;
    return 0;
}

 

posted @ 2019-04-05 16:35  Myrcella  阅读(144)  评论(0)    收藏  举报