分析
对于这类问题,我们先观察是否有什么性质。由于我们要求 的所有数最后都可以到达 。而 在行动时候会经过 的所有节点。那么我们的问题就随之转换为, 到 最少要经过几个区间。那么我们可以先考虑 。令 为考虑到前 个机器,当前点为 的最小步数。那么我们就有两个转移 和 。那么这样我们的总复杂度为 。
但其实我们仔细思考一下,可以发现,我们是否可以从 走到 只与最大值,也就是区间的右端点 有关。所以我们改变一下方程 只在 时才转移。那么这样是不会失去最优答案的,因为对于每个区间,只有我尽量靠右,才可能成为最优值。那么这样我们的时间复杂度为 了。而且观察到 只会从 转移,所以可以考虑滚动数组。这样我们的空间就为 了。
我们发现,我们的瓶颈其实是出现在求解 的最小值的。那么是否有一种工具可以修改单点,而且可以查询区间最小值呢?当然我们可以使用线段树来维护,这样总的复杂度就为 。
这题应该是一道非常经典的线段树优化 的题目了,如果有什么不太明白的,欢迎私聊。
代码
因群众要求,我把 的代码发一下,加深理解。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N = 1e3 + 100; int f[N][N],s[N],t[N],n,m; int main() { scanf("%d%d",&n,&m); for(int i = 1;i <= m;i++) scanf("%d%d",&s[i],&t[i]); memset(f,0x3f,sizeof(f));f[0][1] = 0; for(int i = 1;i <= m;i++) { for(int j = 1;j <= n;j++) { f[i][j] = f[i-1][j]; if(j == t[i]) { for(int k = s[i];k <= t[i];k++) f[i][j] = min(f[i][j],f[i-1][k] + 1); } } } cout << f[m][n] << endl; return 0; }
代码
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N = 4e6 + 100,inf = 0x3f3f3f3f; int S[N],T[N],n,m,t[N]; void update(int u,int l,int r,int pos,int k) { if(l == r) {t[u] = min(k,t[u]);return;} int mid = l + r >> 1; if(pos <= mid) update(u<<1,l,mid,pos,k); else update(u<<1|1,mid+1,r,pos,k); t[u] = min(t[u<<1],t[u<<1|1]); } int ask(int u,int l,int r,int L,int R) { if(l > R || r < L) return inf; if(L <= l && r <= R) return t[u]; int mid = l + r >> 1; return min(ask(u<<1,l,mid,L,R),ask(u<<1|1,mid+1,r,L,R)); } int main() { scanf("%d%d",&n,&m); for(int i = 1;i <= m;i++) scanf("%d%d",&S[i],&T[i]);memset(t,0x3f,sizeof(t)); update(1,1,n,1,0); for(int i = 1;i <= m;i++) { int ans = ask(1,1,n,S[i],T[i]); update(1,1,n,T[i],1 + ans); } printf("%d\n",ask(1,1,n,n,n)); return 0; }