3729 飞扬的小鸟

 时间限制: 1 s
 空间限制: 128000 KB
 题目等级 : 黄金 Gold
 

题目描述 Description

 

输入描述 Input Description

 

输出描述 Output Description

输出文件名为 bird.out。

共两行。

第一行,包含一个整数,如果可以成功完成游戏,则输出 1,否则输出 0。

第二行,包含一个整数,如果第一行为 1,则输出成功完成游戏需要最少点击屏幕数,

否则,输出小鸟最多可以通过多少个管道缝隙。

样例输入 Sample Input

 

 

样例输出 Sample Output

【输入输出样例说明】

如下图所示,蓝色直线表示小鸟的飞行轨迹,红色直线表示管道。

 

数据范围及提示 Data Size & Hint

对于 30%的数据:5≤n≤10,5≤m≤10,k=0,保证存在一组最优解使得同一单位时间最多点击屏幕 3 次;

对于 50%的数据:5≤n≤20,5≤m≤10,保证存在一组最优解使得同一单位时间最多点击屏幕 3 次;

对于 70%的数据:5≤n≤1000,5≤m≤100;

对于 100%的数据: 5≤n≤10000, 5≤m≤1000, 0≤k<n, 0<X<m, 0<Y<m, 0<P<n, 0≤L<H  ≤m,L +1<H。

思路:

首先想到设分f[i][j]表示到达地i行第j列所需要的最少点击屏幕次数。转移方程为

  f[ i ][ j ]=min{f[ i-1 ][ j - k*x[i-1] ] + k} (1<= k <= j/x) 上升—— ①

  f[ i ][ j ]=min{f[ i-1 ][ j + y[i-1] } ( j + y[i-1] <= m) 下降

显然,下降可以O(1)转移,主要问题在上升的转移。

我们将上升的方程变一下:

  f[ i ][ j - x[i-1] ]=min{f[ i-1 ][ (j - x[i-1]) - (k-1)*x[i-1] ] + k -1} ——②

这是 f[ i ][ j - x[i-1] ] 的转移。

由 ② 化简可得:

  f[ i ][ j - x[i-1] ]=min{f[ i-1 ][ j - k*x[ i-1] ] + k -1}

消去f[ i-1 ][ j - k*x[ i-1] ]

  f[ i ][ j ]= f[ i ][ j - x[ i-1 ] ]+1

于是就可以O(n*m)的时间内出解啦~

@大佬%%%

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#define INF 2100000000
#define LL long long
using namespace std;

const int Maxn = 10010;
const int Maxm = 1010;
int n,m,k;
int x[Maxn],y[Maxn];
///到达i行j列所需要的最少点击屏幕的次数 
int dp[Maxn][Maxm];

struct bird {
    int u,d;
}b[Maxn];

int main() {
    scanf("%d%d%d",&n,&m,&k);
    for(int i=0; i<n; ++i)
        scanf("%d%d",&x[i],&y[i]);
    ///初始化各坐标(若不被更新,则说明该横坐标上并不是管道) 
    for(int i=1; i<=n; ++i)
        b[i].d=0,b[i].u=m+1;
    for(int i=0,p; i<k; ++i) {
        scanf("%d",&p);
        scanf("%d%d",&b[p].d,&b[p].u);
    }
    for(int i=1; i<=n; ++i)
        for(int j=0; j<=m; ++j)
            dp[i][j]=INF;///因为要取min,所以需要赋个极大值 
    ///因为j!=0,所以dp[0][0]=INF; 
    dp[0][0]=INF;
    for(int i=1; i<=n; ++i) {
        ///暂时先不考虑管道的情况,进行更新dp数组 
        for(int j=x[i-1]; j<=m; ++j) {
            ///普通的转移(由上一个(i-1)刚好能跳的f[i-1].x的坐标处的值+1进行转移过来) 
            dp[i][j]=min(dp[i][j],dp[i-1][j-x[i-1]]+1);
            ///见公式推出 
            dp[i][j]=min(dp[i][j],dp[i][j-x[i-1]]+1);
            ///因为不能够超过m,会出现比较多种的转移方程 
            if(j==m) 
                for(int o=m-x[i-1]; o<=m; ++o) {
                    ///位于i-1处,并且一跳能够够到m(j)的,用来进行i,j的更新 
                    dp[i][j]=min(dp[i][j],dp[i-1][o]+1); 
                    ///见公式推出 
                    dp[i][j]=min(dp[i][j],dp[i][o]+1);
                }
        }
        ///处理下落的情况,必须是合法的 
        for(int j=b[i].d+1; j<b[i].u; ++j) 
            ///若合法
            if(j+y[i-1]<=m)
                dp[i][j]=min(dp[i][j],dp[i-1][j+y[i-1]]); 
        ///考虑当前管道缝隙的下界以下的地方是没有dp值的,所以重新将其赋值为最大值 
        for(int j=1; j<=b[i].d; ++j)
            dp[i][j]=INF;
        ///上界以上的地方也是没有dp值的 
        for(int j=m; j>=b[i].u; --j)
            dp[i][j]=INF;
    }
    int cnt=k,ans=INF;
    ///需要逆序枚举 
    for(int i=n; i>=1; --i) {
        for(int j=1; j<=m; ++j)
            ans=min(ans,dp[i][j]);
        ///如果ans(i)逆序被更新了,那么他一定就是最优解 
        ///因为胜利的结束条件是飞得远一直到横坐标为n 
        if(ans!=INF)
            break;
        ///如果是管道 
        if(b[i].u<=m)
            cnt--;
    }
    ///在最后一个水管出现之前被break;那么被更新的ans即为最优解 
    if(cnt==k)
        printf("1\n%d\n",ans);
    ///据题目要求输出最多能通过几个管道 
    else
        printf("0\n%d\n",cnt);
    return 0;
}