hdu6076 Security Check 分类dp 思维
/** 题目:hdu6076 Security Check 链接:http://acm.hdu.edu.cn/showproblem.php?pid=6076 题意:有两个队列在排队,每一次警察可以检查其中一个队的队首的一个人,或者两个队的队首同时检查(两个队首的人满足abs(a[i]-b[j])>k) 每检查一次需要1分钟,求警察检查完所有的人需要的最少时间。 思路:一眼看过去可以定义dp[i][j]表示第一个队列[1,i],第二个队列[1,j]检查完需要的最少时间。但是i,j太大了。 看了官方题解是这样做的,由于k比较小,如果abs(a[i]-b[j])<=k,那么只可以二选一去检查一个人。 这个时候可以dp记忆化,第一维记忆i,第二维记忆a[i]-b[j]+k;来唯一标识该状态。(两个队列都是[1,n]的排列) 如果abs(a[i]-b[j])>k;则两个队队首同时检查最佳。如果检查完该次之后还是满足abs(a[i]-b[j])>k,那么仍然同时检查。 为了加快速度,所以用vector维护一个i和j的一个确定偏移量时候,满足abs(a[i]-b[j])<=k的i位置,这样可以二分下一次出现abs(a[i]-b[j])<=k的i位置, 那么这个i和前面那个i之间的区间就是abs(a[i]-b[j])>k的匹配,可以跳跃到二分后的i。 总时间复杂度为O(n*k*lg(n)) */ #include<iostream> #include<cstdio> #include<algorithm> #include<map> #include<vector> #include<queue> #include<set> #include<cstring> #include<cmath> using namespace std; typedef pair<int,int> P; typedef long long LL; const int N = 6e4+10; const int mod = 1000000007; const int INF = 0x3f3f3f3f; int n, k; int a[N], b[N], pos[N]; int dp[N][22]; vector<int> v[N*2]; int dfs(int i,int j) { if(i==0||j==0) return i+j; if(abs(a[i]-b[j])<=k){ int &res = dp[i][a[i]-b[j]+k]; if(~res) return res; return res = min(dfs(i-1,j),dfs(i,j-1))+1; } auto it = upper_bound(v[i-j+n].begin(),v[i-j+n].end(),i); if(it==v[i-j+n].begin()) return max(i,j);///一直到头都没有abs(a[i]-b[j])<=k的。 it--; return dfs(*it,j-(i-*it))+i-*it;///[*it + 1, i]这个区间和[j-(i-*it)+1,j]这个区间对应位置满足abs(a[i]-b[j])>k,所以可以快速处理。 } int main() { int T; cin>>T; while(T--) { scanf("%d%d",&n,&k); for(int i = 1; i <= n; i++) scanf("%d",&a[i]); for(int i = 1; i <= n; i++){ scanf("%d",&b[i]); pos[b[i]] = i; } for(int i = 0; i <= 2*n; i++){ v[i].clear(); } for(int i = 1; i <= n; i++){ for(int j = a[i]-k; j <= a[i]+k; j++){ if(j>=1&&j<=n){ v[i-pos[j]+n].push_back(i); } } } memset(dp, -1, sizeof dp); printf("%d\n",dfs(n,n)); } return 0; }
posted on 2017-08-07 16:42 hnust_accqx 阅读(180) 评论(0) 编辑 收藏 举报