HDU4511 小明系列故事——女友的考验 —— AC自动机 + DP

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

题目链接:https://vjudge.net/problem/HDU-4511

小明系列故事——女友的考验

Time Limit: 500/200 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others)
Total Submission(s): 1924    Accepted Submission(s): 525

Problem Description
  终于放寒假了,小明要和女朋友一起去看电影。这天,女朋友想给小明一个考验,在小明正准备出发的时候,女朋友告诉他,她在电影院等他,小明过来的路线必须满足给定的规则:
  1、假设小明在的位置是1号点,女朋友在的位置是n号点,则他们之间有n-2个点可以走,小明每次走的时候只能走到比当前所在点编号大的位置;
  2、小明来的时候不能按一定的顺序经过某些地方。比如,如果女朋友告诉小明不能经过1 -> 2 -> 3,那么就要求小明来的时候走过的路径不能包含有1 -> 2 -> 3这部分,但是1 -> 3 或者1 -> 2都是可以的,这样的限制路径可能有多条。
  这让小明非常头痛,现在他把问题交给了你。
  特别说明,如果1 2 3这三个点共线,但是小明是直接从1到3然后再从3继续,那么此种情况是不认为小明经过了2这个点的。
  现在,小明即想走最短的路尽快见到女朋友,又不想打破女朋友的规定,你能帮助小明解决这个问题吗?
 
Input
  输入包含多组样例,每组样例首先包含两个整数n和m,其中n代表有n个点,小明在1号点,女朋友在n号点,m代表小明的女朋友有m个要求;
  接下来n行每行输入2个整数x 和y(x和y均在int范围),代表这n个点的位置(点的编号从1到n);
  再接着是m个要求,每个要求2行,首先一行是一个k,表示这个要求和k个点有关,然后是顺序给出的k个点编号,代表小明不能走k1 -> k2 -> k3 ……-> ki这个顺序的路径;
  n 和 m等于0的时候输入结束。

  [Technical Specification]
  2 <= n <= 50
  1 <= m <= 100
  2 <= k <= 5

 
Output
  对于每个样例,如果存在满足要求的最短路径,请输出这个最短路径,结果保留两位小数;否则,请输出”Can not be reached!” (引号不用输出)。
 
Sample Input
3 1
1 1
2 1
3 1
2
1 2

2 1
0 0
1 1
2
1 2

5 3
0 0
5 3
1 2
1 22
5 21
3
1 2 3
2
4 5
2
1 5

0 0

 
Sample Output
2.00
Can not be reached!
21.65
 
Source

题意:

有n个点(1~n)且已知每个点的坐标。求点1走到点n的最短路径,要求路径上点的标号递增,并且不能走给出的m条禁止路线。

题解:

1.将m条进制路线插入AC自动机中。

2.设dp[i][j]为:当前点为i,且到达的状态为j(自动机上的状态)的最短路径(从点1到点i)。

3.由于题目规定了“路径上点的标号递增”,那么可以直接递推求解:dp[k][newj] = min(dp[k][newj], dp[i][j]+dis(i,k)),其中 i+1<=k<=n,newj为下一个状态。

代码如下:

 #include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <string>
#include <set>
using namespace std;
typedef long long LL;
const double EPS = 1e-;
const int INF = 2e9;
const LL LNF = 2e18;
const int MOD = 1e5;
const int MAXN = 1e3+; double x[], y[], dp[MAXN][MAXN]; double dis(int i, int j)
{
return sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
} struct Trie
{
int sz, base;
int next[MAXN][], fail[MAXN], end[MAXN];
int root, L;
int newnode()
{
for(int i = ; i<sz; i++)
next[L][i] = -;
end[L++] = false;
return L-;
}
void init(int _sz, int _base)
{
sz = _sz;
base = _base;
L = ;
root = newnode();
}
void insert(int buf[], int len)
{
int now = root;
for(int i = ; i<len; i++)
{
if(next[now][buf[i]-base] == -) next[now][buf[i]-base] = newnode();
now = next[now][buf[i]-base];
}
end[now] |= true;
}
void build()
{
queue<int>Q;
fail[root] = root;
for(int i = ; i<sz; i++)
{
if(next[root][i] == -) next[root][i] = root;
else fail[next[root][i]] = root, Q.push(next[root][i]);
}
while(!Q.empty())
{
int now = Q.front();
Q.pop();
end[now] |= end[fail[now]];
for(int i = ; i<sz; i++)
{
if(next[now][i] == -) next[now][i] = next[fail[now]][i];
else fail[next[now][i]] = next[fail[now]][i], Q.push(next[now][i]);
}
}
} double query(int n)
{
for(int i = ; i<=n; i++)
for(int j = ; j<L; j++)
dp[i][j] = LNF; dp[][next[root][-base]] = ; //注意,其实状态不是root,而是next[root][1-base],因为从点1开始。
for(int i = ; i<n; i++)
for(int j = ; j<L; j++)
{
if(end[j] || dp[i][j]>=LNF) continue;
for(int k = i+; k<=n; k++)
{
int newj = next[j][k-base];
if(end[newj]) continue;
dp[k][newj] = min(dp[k][newj], dp[i][j]+dis(i,k));
}
}
double ret = LNF;
for(int i = ; i<L; i++)
ret = min(ret, dp[n][i]);
return ret;
}
}; Trie ac;
int buf[MAXN];
int main()
{
int n, m, k;
while(scanf("%d%d", &n,&m) && (n||m))
{
ac.init(n,);
for(int i = ; i<=n; i++)
scanf("%lf%lf", &x[i], &y[i]);
while(m--)
{
scanf("%d", &k);
for(int i = ; i<k; i++)
scanf("%d", &buf[i]);
ac.insert(buf, k);
} ac.build();
double ans = ac.query(n);
if(ans>=LNF) printf("Can not be reached!\n");
else printf("%.2f\n", ans);
}
return ;
}