HDU 4511 小明系列故事——女友的考验 ( Trie图 && DP )
题意 : 给出编号从1 ~ n 的 n 个平面直角坐标系上的点,求从给出的第一个点出发到达最后一个点的最短路径,其中有两种限制,其一就是只能从编号小的点到达编号大的点,再者不能走接下来给出的 m 个限制路径,也就是其中有些路线无法走。
分析 : 把问题抽象一下就是用编号 1 ~ n 构造一个字符串,使得字符串不包含 m 个给出的子串,且构造的代价是最小的 ( 两点间距就是代价,也就是路长 )。那如果做了很多有关在 Trie图 || AC自动机上DP的题目,那这道题就使用很套路的方法就可以了。先将给出的 m 个路线丢去构建 Trie 图,然后定义 DP[i][j] = 在编号为 i 的点 且 在 Trie 图上编号为 j 的节点状态下,最短的路径长度是多少,则可得转移方程
DP[i+1][k] = min( DP[i+1][k], DP[i][j] + GetDis(i, k) ) 且 i+1 <= k <= n (保证编号从小到大)
其中 GetDis(i, k) 是坐标点 i 和 k 的距离、而且 Trie[j].Next[k].flag == 0 即在 Trie 图上 j 状态到 k 状态合法,并不会使其包含非法路径
#include<stdio.h> #include<string.h> #include<algorithm> #include<queue> #include<math.h> using namespace std; const int Max_Tot = 555; const int Letter = 55; const double INF = 1e20;///不能使用 0x3f3f3f3f int n, m; pair<int, int> Point[55]; struct Aho{ struct StateTable{ int Next[Letter]; int fail, flag; }Node[Max_Tot]; int Size; queue<int> que; inline void init(){ while(!que.empty()) que.pop(); memset(Node[0].Next, 0, sizeof(Node[0].Next)); Node[0].fail = Node[0].flag = 0; Size = 1; } inline void insert(int *s, int len){ int now = 0; for(int i=0; i<len; i++){ int idx = s[i]; if(!Node[now].Next[idx]){ memset(Node[Size].Next, 0, sizeof(Node[Size].Next)); Node[Size].fail = Node[Size].flag = 0; Node[now].Next[idx] = Size++; } now = Node[now].Next[idx]; } Node[now].flag = 1; } inline void BuildFail(){ Node[0].fail = 0; for(int i=0; i<n; i++){ if(Node[0].Next[i]){ Node[Node[0].Next[i]].fail = 0; que.push(Node[0].Next[i]); }else Node[0].Next[i] = 0;///必定指向根节点 } while(!que.empty()){ int top = que.front(); que.pop(); Node[top].flag |= Node[Node[top].fail].flag;///注意了!! for(int i=0; i<n; i++){ int &v = Node[top].Next[i]; if(v){ que.push(v); Node[v].fail = Node[Node[top].fail].Next[i]; }else v = Node[Node[top].fail].Next[i]; } } } }ac; double GetDis(pair<int,int> &st, pair<int,int> &en) { double x1 = (double)st.first; double x2 = (double)en.first; double y1 = (double)st.second; double y2 = (double)en.second; return sqrt( (double)(1.0*x1-x2)*(double)(1.0*x1-x2) + (double)(1.0*y1-y2)*(double)(1.0*y1-y2)); } double dp[55][555]; inline void Solve() { for(int i=0; i<n; i++) for(int j=0; j<ac.Size; j++) dp[i][j] = INF; dp[0][ac.Node[0].Next[0]] = 0; for(int i=0; i<n-1; i++){ for(int j=0; j<ac.Size; j++){ if(dp[i][j] < INF){ for(int k=i+1; k<n; k++){ int newi = k; int newj = ac.Node[j].Next[k]; if(!ac.Node[ newj ].flag){ dp[newi][newj] = min(dp[newi][newj], dp[i][j]+GetDis(Point[i], Point[k])); } } } } } double ans = INF; for(int i=0; i<ac.Size; i++) if(dp[n-1][i] < INF) ans = min(ans, dp[n-1][i]); if(ans == INF) puts("Can not be reached!"); else printf("%.2lf\n", ans); } int main(void) { while(~scanf("%d %d", &n, &m)){ if(n==0 && m==0) break; for(int i=0; i<n; i++)///这里我将编号 -1 了,也就是编号从 0 ~ n-1,所以下面操作都是按个编号来 scanf("%d %d", &Point[i].first, &Point[i].second); int tmp[10]; ac.init(); while(m--){ int k; scanf("%d", &k); for(int i=0; i<k; i++){ scanf("%d", &tmp[i]); tmp[i]--;///因为编号是 0 ~ n-1 } ac.insert(tmp, k); }ac.BuildFail(); Solve(); } return 0; }