小6将这些骨牌从左到右排成一排立起来,如果向左或者向右推倒其中一个骨牌,那么它碰到左边或者右边的骨牌会一起连续倒下。
也就是说,每个骨牌只能向左或者向右倒下。
小6想知道,最少需要直接推倒多少骨牌,才能把所有的骨牌全都放倒。
第一行是一个整数T,表示数据组数。(T≤20)
每组输入中的第一行为多米诺骨牌数量N(1≤N≤100),
接着有N行,第i行有两个正整数Pi、Hi分别代表多米诺骨牌的横坐标和该骨牌的高度,数据保证每个坐标最多只有一个骨牌,并且所给出的Pi是递增的。
(1≤Pi, Hi≤1000)
对于每组输入,在一行中输出一个整数,表示要把所有骨牌直接或间接放倒最少需要直接推倒的骨牌数量。
#include <bits/stdc++.h> #include <ext/hash_map> #include <ext/hash_set> #include <ext/pb_ds/tree_policy.hpp> #include <ext/pb_ds/assoc_container.hpp> #include <ext/pb_ds/priority_queue.hpp> 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<N;X++) #define REP2(X,L,R) for(int X=L;X<=R;X++) #define DEP(X,R,L) for(int X=R;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<int, int> PII; typedef vector<int> VI; typedef vector<PII> 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'<<" "<<j+1<<" "<<dp[j+1]<<endl; r = max(r, p[j]+h[j]);//这里r表示的是:从i到j可以砸倒的最大位置 } else break; } //相比于上面这里要简单一些 int l = p[i]; REP2(j, i, n-1) { if (p[j] - h[j] < l) { dp[j+1] = min(dp[j+1], dp[i]+1);//理解了上面的,这里就好懂了 //可以把这句话输出出来看结果 //cout<<'2'<<" "<<j+1<<" "<<dp[j+1]<<endl; l = p[j];//通过更新l逐一找到能将i砸到的牌x,以及能将x砸到的牌 } } } cout << dp[n] << endl;//dp[n]便是能将第n张牌砸到的最小需要推几次了。 } return 0; }//自己的理解
#include<bits/stdc++.h> 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]<r){ dp[j] = min(dp[j],dp[i-1]+1); r = max(p[j]+h[j],r); } else break; } int l = p[i]; for(int j = i;j<=n;j++){ if(p[j] - h[j] < l){ dp[j] = min(dp[j],dp[i-1]+1); l = p[j]; } } } cout<<dp[n]<<endl; } return 0; }