Codeforces 677D - Vanya and Treasure - [DP+优先队列BFS]
题目链接:http://codeforces.com/problemset/problem/677/D
题意:
有 $n \times m$ 的网格,每个网格上有一个棋子,棋子种类为 $t[i][j]$,棋子的种类数为 $p$。
现在出发点为 $(1,1)$,必须按照种类 $1 \sim p$ 进行移动,即从种类 $x$ 的棋子出发,下一个目标必须是 $x+1$ 才行,直到走到种类为 $p$ 的棋子就终止。求最短路径。
题解:
我们先把棋子按照种类分组,分成 $p$ 组。
$dp[i][j]$ 表示到达目前这个棋子的最短路,那么转移方程为 $dp[i][j] = min(dp[i][j],dp[x][y]+|x-i|+|y-j|)$,其中 $(x,y)$ 为上一组中所有棋子的坐标。
然后要是直接暴力的状态转移的话,是要TLE的,考虑进行优化。
考虑一个界限 $K$,假设当前组为 $T[i]$,上一组为 $T[i-1]$,
那么当 $T[i].size \le K$ 时,我们就用继续用上面的暴力动态转移,那么对于所有的“上一组”点数加起来不会差过 $nm$,因此总时间复杂度 $O(K \cdot nm)$;
如果 $T[i].size > K$,我们在网格上进行多源点的优先队列BFS(或者说优先队列dijkstra),源点是所有的 $T[i-1]$ 组内的点,搜出到所有 $T[i]$ 组内的点的最短距离,这样BFS最多跑一遍所有网格,时间复杂度 $O(nm \log(nm))$;由于这样的组数目不会超过 $\frac{nm}{K}$ 个,所以总时间复杂度为 $O(\frac{nm}{K} nm \log(nm) )$。
这样一来,两种加起来的总时间复杂度就是 $O(Knm+\frac{nm}{K}nm\log(nm) ) = O( nm (K + \frac{nm\log(nm)}{K}) )$,由此可知取 $K=\sqrt{nm \log(nm) }$ 时,时间复杂度最小,为 $O(nm\sqrt{nm\log(nm)})$。
AC代码:
#include<bits/stdc++.h> #define idx(x,y) ((x-1)*m+y) #define mp(x,y) make_pair(x,y) #define fi first #define se second using namespace std; typedef pair<int,int> P; const int INF=0x3f3f3f3f; const int maxn=305; int n,m,p,ed,K; P pos[maxn*maxn]; vector<int> T[maxn*maxn]; int dp[maxn*maxn]; int dist(const P& u,const P& v) { return abs(u.fi-v.fi)+abs(u.se-v.se); } int dx[4]={1,0,-1,0}; int dy[4]={0,1,0,-1}; int d[maxn*maxn]; bool vis[maxn*maxn]; priority_queue< P, vector<P>, greater<P> > Q; int main() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); cin>>n>>m>>p; K=sqrt(n*m*log2(n*m)); for(int i=1,tp;i<=n;i++) { for(int j=1;j<=m;j++) { cin>>tp; T[tp].push_back(idx(i,j)); pos[idx(i,j)]=mp(i,j); if(tp==p) ed=idx(i,j); } } memset(dp,0x3f,sizeof(dp)); for(auto v:T[1]) dp[v]=dist(mp(1,1),pos[v]); for(int i=2;i<=p;i++) { if(T[i].size()<=K) { for(auto v:T[i]) for(auto u:T[i-1]) dp[v]=min(dp[v],dp[u]+dist(pos[u],pos[v])); } else { memset(d,0x3f,sizeof(d)); memset(vis,0,sizeof(vis)); for(auto u:T[i-1]) d[u]=dp[u], Q.push(mp(d[u],u)); while(Q.size()) { int u=Q.top().se; Q.pop(); if(vis[u]) continue; vis[u]=1; for(int k=0;k<4;k++) { if(pos[u].fi+dx[k]<1 || pos[u].fi+dx[k]>n) continue; if(pos[u].se+dy[k]<1 || pos[u].se+dy[k]>n) continue; int v=idx(pos[u].fi+dx[k],pos[u].se+dy[k]); if(vis[v]) continue; if(d[v]>d[u]+1) d[v]=d[u]+1, Q.push(mp(d[v],v)); } } for(auto v:T[i]) dp[v]=d[v]; } } cout<<dp[ed]<<endl; }