洛谷 P3842 [TJOI2007] 线段

思路:DP

这里的状态方程不算好想,这里的dp含义是在第i行走完线段之后我们在哪一个端点上(左右端点)的最小路数。

这道题需要考虑的情况确实比较多,我们需要考虑这样几种情况:

1.我们在每一行走完之后,会停留在哪一个顶点上?是左端点还是右端点?

2.我们该怎么走?

首先解决第一个问题,从第i行举例子,假如说我们最后需要在i行走完线段之后停留在了左端点,那么首先我们需要考虑我们上一行已经走过的路数,需要加上上面的路数即+dp[i-1][0]或者+dp[i-1][1]。这里又有一个新问题,那就是上一行走完线段之后的停留端点和下一行我们走路的时候有关系吗?

有关系的,因为在左右端点是需要我们去判断的,也就是说,不仅需要判断我们在本行在哪个端点停留,还需要知道我们在上一行是停留在哪。

接着上面的说:

(1)我们在上一行的左端点停留了,我们需要求出来在本一行在走完线段之后在左端点停留的最小路数。首先,我们除了需要加上上一行走的路数之外,需要+1,因为我们需要向下面走一步,走到下面的行数,然后,因为我们在左端点,到达这一行的左端点,需要先走完线段,所以我们需要走到这一行的右端点上,然后再回到这一行的左端点上(不是上一行的左端点,不要搞混),也就是abs(r[i]-l[i-1])+(r[i]-l[i]),前一个括号代表的是我们首先走到下一行的右端点上,后面括号里面代表的是我们再从这一行的右端点再走到左端点上。

(2)我们在上一行的右端点停留了,然后我们需要求出来在本行走完线段之后的右端点停留的最小路数,同理,首先加上上一行我们在上一行右端点停留的路数,然后,+1,意思一样,就是向下走了一行,然后,我们还是需要先走到本行右端点,再走到本行的左端点,也就是

abs(r[i]-r[-1])+(r[i]-l[i]).

总和上面这两种可能,我们写出来了第一个状态方程:

dp[i][0]=min(dp[i-1][0]+1+abs(r[i]-l[i-1])+r[i]-l[i],dp[i-1][1]+1+abs(r[i]-l[i-1])+r[i]-l[i])

同理,当我们走到本行的右端点的时候也是这个思路去考虑,这不过是变了一个路线,也就是在本行的时候我们需要先走到左端点,再走到右端点。

dp[i][1]=min(dp[i-1][0]+1+abs(l[i]-l[i-1])+r[i]-l[i],dp[i-1][1]+1+abs(l[i]-r[i-1])+r[i]-l[i])

最后,我们不要忘记,在走到最后一行之后我们就停止了,也就是停在了最后一行的线段的两端,这个时候我们不一定在最后的终点上,所以我们需要再对我们目前所在的端点距离终点的路数加上,然后判断在这两个端点处哪一个路径是最短的。

再强调一遍,那就是我们一直这样走来走去的,是为了走完线段。

注意:我们首先对于第一行初始化,然后往后推就行了,如果不初始化是不会对的。

上代码:

#include<iostream>
#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<cmath> 
#include<vector>
#include<algorithm>
#include<stack>
#include<queue>
#include<deque>
#include <iomanip>
#include<sstream>
#include<numeric>
#include<map>
#include<limits.h>
#include<unordered_map>
#include<set>
#define int long long
#define MAX 20010
#define inf 0x3f3f3f3f
#define _for(i,a,b) for(int i=a;i<(b);i++)
using namespace std;
typedef pair<int, int> PII;
int n;
int counts;
int dx[] = { 0,1,0,-1};
int dy[] = { 1,0,-1,0 };
int dp[MAX][2];
int l[MAX], r[MAX];
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> l[i] >> r[i];
    }
    dp[1][1] = r[1] - 1;
    dp[1][0] = r[1] - 1 + r[1] - l[1];
    for (int i = 2; i <= n; i++) {
        dp[i][0] = min(dp[i - 1][0] + 1 + abs(r[i] - l[i - 1]) + r[i] - l[i], dp[i - 1][1] + 1 + abs(r[i] - r[i - 1]) + r[i] - l[i]);
        dp[i][1] = min(dp[i - 1][0] + 1 + abs(l[i] - l[i - 1]) + r[i] - l[i], dp[i - 1][1] + 1 + abs(r[i - 1] - l[i]) + r[i] - l[i]);
    }
    cout << min(dp[n][0]+n-l[n], dp[n][1]+n-r[n]);
    return 0;
}

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值