题意 : 给出编号从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 double INF = 1e20;///不能使用 0x3f3f3f3f int n, m; pair<]; 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[].Next, , ].Next)); Node[].fail = Node[].flag = ; Size = ; } inline void insert(int *s, int len){ ; ; i<len; i++){ int idx = s[i]; if(!Node[now].Next[idx]){ memset(Node[Size].Next, , sizeof(Node[Size].Next)); Node[Size].fail = Node[Size].flag = ; Node[now].Next[idx] = Size++; } now = Node[now].Next[idx]; } Node[now].flag = ; } inline void BuildFail(){ Node[].fail = ; ; i<n; i++){ ].Next[i]){ Node[Node[].Next[i]].fail = ; que.push(Node[].Next[i]); }].Next[i] = ;///必定指向根节点 } while(!que.empty()){ int top = que.front(); que.pop(); Node[top].flag |= Node[Node[top].fail].flag;///注意了!! ; 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)); } ][]; inline void Solve() { ; i<n; i++) ; j<ac.Size; j++) dp[i][j] = INF; dp[][ac.Node[].Next[]] = ; ; i<n-; i++){ ; j<ac.Size; j++){ if(dp[i][j] < INF){ ; 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; ; i<ac.Size; i++) ][i] < INF) ans = min(ans, dp[n-][i]); if(ans == INF) puts("Can not be reached!"); else printf("%.2lf\n", ans); } int main(void) { while(~scanf("%d %d", &n, &m)){ && m==) break; ; i<n; i++)///这里我将编号 -1 了,也就是编号从 0 ~ n-1,所以下面操作都是按个编号来 scanf("%d %d", &Point[i].first, &Point[i].second); ]; ac.init(); while(m--){ int k; scanf("%d", &k); ; i<k; i++){ scanf("%d", &tmp[i]); tmp[i]--;///因为编号是 0 ~ n-1 } ac.insert(tmp, k); }ac.BuildFail(); Solve(); } ; }