该题需要用数据结构来优化DP ,具体方法就是之前第八章讲的(用数据结构优化算法,紫书P241),使用一个数组和两个指针维护一个单调队列, 可以在O(n)的时间内求出滑动窗口中的最小值 。
有了这个优化我们就可以快速的求出dp[i-1][j](x-d<=j<=x+d)的最小值。
然而刘汝佳就是不这么做,他只用了一个指针,连维护优先队列的数组都没开,就“隐式的”求出了最小值 。
具体做法是:
1.先维护窗口左边界,别让指针k超出了窗口,如果x[k] < x[j] - d那么就k++ (因为x数组是从大到小已经排好序的),然后在不超出右边界x[j]+d 的前提下,如果dp[i][k+1] <= dp[i][k],那么k++;
为什么这样是对的的? 好像和之前说的优先队列一点也不一样啊! 其实是一样的操作,仔细回想维护优先队列时是怎么操作的 :用两个指针front、rear 先更新左边界,防止他超出边界,一旦超出就front++; 然后每次新加进来一个值就要看看当前队列最右端的元素与新值的大小,如果大于新值那么就rear--,将无用的元素请出队列 ,直到小于新值,就将新值加入, 然而其实上边那个用一个指针的方法是如出一辙的,只不过将删除无用值这一步放到了求最小值时,也就是更新k时 。
通过该题,对优先队列求区间最小值又有了一个新的认识,更加深入的理解了这种巧妙的构造方式 。 另外值得一提的是,小小的滑动窗口还有很多变形,有的时候窗口的大小还是可变的 。 如何优化、如何维护,还要去通过紫书慢慢体会 。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 100 + 5; const int maxx = maxn*maxn*2; const ll INF = (1ll << 60); int T,n,d; ll h[maxn],x[maxx],dp[2][maxx]; int main() { scanf("%d",&T); while(T--) { scanf("%d%d",&n,&d); for(int i=0;i<n;i++) scanf("%lld",&h[i]); if(abs(h[0] - h[n-1]) > (n-1)*d) { //这样,无论如何都至少有两个点差距大于d printf("impossible\n"); continue; } int nx = 0; for(int i=0;i<n;i++) for(int j=-n+1;j<=n-1;j++) x[nx++] = h[i] + j*d; //先将所有可能情况全部取出,排序、去重,方便组织优先队列 sort(x,x+nx); nx = unique(x,x+nx) - x; int t = 0; for(int i=0;i<=nx;i++) { dp[0][i] = INF; if(x[i] == h[0]) dp[0][i] = 0; //边界处理大法 } for(int i=1;i<n;i++) { int k = 0; for(int j=0;j<nx;j++) { while(k < nx && x[k] < x[j]-d) k++; while(k+1 < nx && x[k+1] <= x[j]+d && dp[t][k+1] <= dp[t][k]) k++; //利用优先队列求最小值 if(dp[t][k] == INF) dp[t^1][j] = INF; else dp[t^1][j] = dp[t][k] + abs(x[j] - h[i]); } t ^= 1; //使用滚动数组节省内存 } for(int i=0;i<nx;i++) if(x[i] == h[n-1]) { printf("%lld\n",dp[t][i]); break; } } return 0; }