思路:
对于2个点中间的所有点,其实都可以看做一个点(因为移动他们无需任何代价)
那么我们把所有点的下一个点
(不能为0) 放入集合,对这些点进行dp。
定义dp[i]表示到达第
个点都安全的最小代价。
那么转移:
-
如果下一个点是已经伸出的点:
那么下一个点 dp[i+1] = min(dp[i+1],dp[i]+1) 因为改变这个点必会改变下一个点的状态,要让它保持不变,代价+1.
下个点伸出,下下一个点如果是已经伸出的点,那么 dp[i+2] = min(dp[i+2],dp[i]) 因为可以直接落下去
下个点伸出,下下一个点没有伸出,dp[i+2] = min(dp[i+2],dp[i]+1) 落下的距离必须小于等于2,所以必须改变其状态。 -
如果下一个点没有伸出:
那么下一个点 dp[i+1] = min(dp[i+1],dp[i]); 改变其状态刚好可以接住。
对于下下个点 dp[i+2] = min(dp[i+2],dp[i]+1); 因为下下个点必定是伸出的,因为中间只能夹着一个没伸出的点,所以必须+1.
code
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int man = 2e5+10;
#define IOS ios::sync_with_stdio(0)
#define endl '\n'
#define ll long long
const ll mod = 1e9+7;
map<int,int>vis;
struct cmp{
bool operator()(const int a,const int b){
return a > b;
}
};
int dp[2*man];
vector<int>v;
signed main() {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
//freopen("out.txt","w",stdout);
#endif
int t;
scanf("%d",&t);
while(t--){
int h,n;
vis.clear();
scanf("%d%d",&h,&n);
set<int,cmp>sp;
memset(dp,0x3f,sizeof(int)*(2*n+5));
for(int i = 1;i <= n;i++){
int x;cin >> x;
vis[x] = 1;
sp.insert(x);
if(x-1)sp.insert(x-1);
}
v.clear();
for(auto it:sp){
v.emplace_back(it);
}
dp[0] = 0;
for(int i = 0;i < v.size()-1;i++){
if(vis.count(v[i+1])){
dp[i+1] = min(dp[i+1],dp[i]+1);
if(i+2<v.size()&&vis.count(v[i+2]))dp[i+2] = min(dp[i+2],dp[i]);
else if(i+2<v.size())dp[i+2] = min(dp[i+2],dp[i]+1);
}
else{
dp[i+1] = min(dp[i+1],dp[i]);
if(i+2<v.size())dp[i+2] = min(dp[i+2],dp[i]+1);
}
}
int len = v.size()-1;
// for(int i = 0;i <= len;i++){
// printf("va:%d dp[%d]:%d\n",v[i],i,dp[i]);
// }
cout << min(dp[len],dp[len-1]) << endl;
}
return 0;
}