HDU 4511 小明系列故事——女友的考验 ( Trie图 && DP )

时间:2022-12-02 05:07:32

题意 :  给出编号从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();
    }
    ;
}