shuoj-小6的多米诺骨牌-双向dp

Description

6有一副多米诺骨牌,它们的高度不一,且不计厚度。

6将这些骨牌从左到右排成一排立起来,如果向左或者向右推倒其中一个骨牌,那么它碰到左边或者右边的骨牌会一起连续倒下。

也就是说,每个骨牌只能向左或者向右倒下。

6想知道,最少需要直接推倒多少骨牌,才能把所有的骨牌全都放倒。

Input

第一行是一个整数T,表示数据组数。(T20)

每组输入中的第一行为多米诺骨牌数量N(1≤N≤100),

接着有N行,第i行有两个正整数Pi、Hi分别代表多米诺骨牌的横坐标和该骨牌的高度,数据保证每个坐标最多只有一个骨牌,并且所给出的Pi是递增的。

(1Pi, Hi1000)

Output

对于每组输入,在一行中输出一个整数,表示要把所有骨牌直接或间接放倒最少需要直接推倒的骨牌数量。

Sample Input

2
3
1 3
4 2
5 2
3
1 3
4 3
7 7

Sample Output

2
1
题解::自认为这道题的解法很难想明白,从第一次见到这个题也有几个月了。直到前几天看到题解才大概有了一点感觉,今天想这道题想了很久,也演示了几遍,终于搞清楚了一点。这道题的思路是这样的:
(这里输入骨牌从0开始,dp从1开始)用dp[ i ]记录第 i 个骨牌最少能被几次推到,这样dp[ n ]就是要求的结果。更新的原则是: 从左向右遍历一遍骨牌,先从左向右更新当前骨牌能够放倒的所有骨牌,然后从右更新(更新的骨牌为能够从右向左砸到当前骨牌的牌以及能够砸到所有能被更新的骨牌的所有骨牌)。在这里举个例子(1,3)(2,4)(5,2)(7,6)(10,1)(11,3)当遍历第一个节点(1,3)的时候从左更新会更新(1,3)(2,4)(5,2)【这里不解释】,从右更新会更新(1,3)(2,4)(7,6),【这里第二张(2,4)能够砸倒(1,3)更新第二张,(7,6)能够砸倒(2,4)更新第三张,(10,1)不能砸倒(7,6)不更新第四张,就是这样的原则。
首先要把dp[ ] 初始化为无穷大INF,在从左向右方向的过程中状态转移方程为dp[ j +1]  =  min(dp[ j +1] , dp[ i ]+1) , 其中j为要更新的牌,i为当前遍历到的第几张。在p[j]+在从右向左方向的过程中状态转移方程为dp[ j +1] = min(dp[ j +1],dp[ i ]+1);

这里给出一组样例,以及输出,看了程序后跟着程序一步一步的走就明白意思了。

1 
6
1 3
4 2
5 2
7 6
10 1
11 3
shuoj-小6的多米诺骨牌-双向dp_第1张图片
代码::

#include 
#include 
#include 
#include 
#include 
#include 
using namespace __gnu_pbds;
using namespace std;
#define INF 0x3f3f3f3f
#define PB push_back
#define MP make_pair
#define REP(X,N) for(int X=0;X=L;X--)
#define CLR(A,X) memset(A,X,sizeof(A))
#define IT iterator
#define RIT reverse_iterator
typedef long long LL;
typedef unsigned long long uLL;
typedef pair PII;
typedef vector VI;
typedef vector VII;
#define PQ std::priority_queue
#define HEAP __gnu_pbds::priority_queue
#define X first
#define Y second
#define lson l,m,o<<1
#define rson m+1,r,o<<1|1
/********************************
************头文件***************
********************************/
int p[110];
int h[110];
int dp[110];

int main() {
    ios::sync_with_stdio(false);
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        CLR(dp,INF);
        REP(i, n) cin >> p[i] >> h[i];
        dp[0] = 0;
        REP(i, n) {
			//首先从左向右倒
            int r = p[i] + 1;//要确保在跟自己进行比较的时候一定会把自己推到
			//这里要注明下面的p[x]表示第x+1块多米诺牌,dp[x]表示第x块多米诺牌最少第几次被推倒
            REP2(j, i, n-1) {
                if (p[j] < r) {//说明可以推到;p[j]和下面的dp[j+1]指的是一块牌
                    dp[j+1] = min(dp[j+1], dp[i]+1);
					//这里应用的很巧妙,当i==j是必然是可以进入循环的
						//如果dp[j+1]没有被更新过,说明以前不会被推倒,它被推倒的次数最小值就会比上一张牌多一,
						//但是如果被更新过,说明上一张牌可以把这一张砸到,那么dp[i]==dp[j+1],所以最小值仍为dp[j+1]
					//当j>i的时候要进入循环必然是能被砸到的,这时候dp[j+1]肯定应该等于dp[j]
					//这里很难想明白,可以把下面这句话输出来,很有助于理解
                    //cout<<'1'<<" "<

这道题暂时告一段落,还需要继续学习……

2015/10/10
今天终于又看到这道题,感觉理解深刻了很多。dp的作用就是将复杂过程分解成简单的步骤地推出来。贴出新的代码,虽然没有太大的改进,但是思路变得简单了(就是原来太麻烦了……)
#include
using namespace std;
#define INF 0x3f3f3f3f
const int maxn = 110;
int p[maxn];
int h[maxn];
int dp[maxn];

int main(){
	int t;
	cin>>t;
	while(t--){
		int n;
		cin>>n;
		memset(dp,INF,sizeof(dp));
		/**记录节点从 1开始,思考更方便(统一dp和p,h的索引)*/
		for(int i = 1;i<=n;i++) cin>>p[i]>>h[i];
		/**初始化dp[0]是为dp[1]服务*/
		dp[0] = 0;
		for(int i = 1;i<=n;i++){
			/*r和l的作用是不一样的,向左和向右的思路是不一样的。因为向右为正序,更新后的r
			为向右推倒的最大位置。向左为逆序,l表示当前要被推到的位置。所以在向右的时候else
			要break,因为再进行下去已经没有意义了。而向左则不一样,即使位置较大的p也可能推到当前的位置。
			这样就有一个弊端,dp[i]并不一定为推到i的最小次数(好像也没什么卵用)不用管**/
			int r = p[i]+h[i];
		for(int j = i;j<=n;j++){
			if(p[j]





你可能感兴趣的:(dp,shuoj,双向)