题目大意:
终于放寒假了,小明要和女朋友一起去看电影。这天,女朋友想给小明一个考验,在小明正准备出发的时候,女朋友告诉他,她在电影院等他,小明过来的路线必须满足给定的规则:
1、假设小明在的位置是1号点,女朋友在的位置是n号点,则他们之间有n-2个点可以走,小明每次走的时候只能走到比当前所在点编号大的位置;
2、小明来的时候不能按一定的顺序经过某些地方。比如,如果女朋友告诉小明不能经过1 -> 2 -> 3,那么就要求小明来的时候走过的路径不能包含有1 -> 2 -> 3这部分,但是1 -> 3 或者1 -> 2都是可以的,这样的限制路径可能有多条。
这让小明非常头痛,现在他把问题交给了你。
特别说明,如果1 2 3这三个点共线,但是小明是直接从1到3然后再从3继续,那么此种情况是不认为小明经过了2这个点的。
现在,小明即想走最短的路尽快见到女朋友,又不想打破女朋友的规定,你能帮助小明解决这个问题吗?
接下来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
解题报告:这题初看没什么头续,看到了这些路径,然后又想看的数据范围,K很好,想到了Trie,然后又想到了字符串匹配,于是想到了AC自动机。
想到了AC自动机,这题就好做了,就是一个AC自动机DP嘛。
把题目给的非法路径构成一棵Trie树,然做跑一次AC自动机。
然后做DP
dp[i][j]代表在第i个点自动机状态在j的最小距离。
然后枚举可走的点去转移就行了。
自动机状态最多有5*100个。
总的DP状态有50*500,每一个转移是50,最后的复杂度是50*50*500
刚刚过题目。
PS:一次AC,很高兴。
#include<stdio.h>
#include<math.h>
#include<string.h>
const int MAX=1005;
const int MAXSON=50;
struct
{
int id,next[MAXSON],fail;
}node[1000000];
int n,tot;
char mod[1000005];
int len[MAX];
int q[1000000];
void clr()
{
int i;
tot++;
for(i=0;i<MAXSON;i++)
node[tot].next[i]=0;
node[tot].id=node[tot].fail=0;
}
void ins()
{
int tmp,i,n;
int h=0;
scanf("%d",&n);
while(n--)
{
scanf("%d",&tmp);
tmp--;
if(node[h].next[tmp]==0)
{
clr();
node[h].next[tmp]=tot;
}
h=node[h].next[tmp];
}
node[h].id++;
}
void get_fail()
{
int h=0,i;
int f=-1,r=0;
int tmp,fail,k;
q[0]=0;
while(f!=r)
{
tmp=q[++f];
if(node[node[tmp].fail].id>0)//自动机添加一个往前走的东西。错在这里啊,好久没有用AC自动机了
node[tmp].id=1;
for(i=0;i<MAXSON;i++)
{
if(node[tmp].next[i]==0)
{
fail=node[tmp].fail;
node[tmp].next[i]=node[fail].next[i];
continue;
}
k=node[tmp].next[i];
fail=node[tmp].fail;
if(node[fail].next[i]!=k)
node[k].fail=node[fail].next[i];
else
node[k].fail=0;
q[++r]=k;
}
}
}
const double EPS=1.0e-8;
bool dblcmp(double x)
{
if(fabs(x)<EPS)return 0;
return x<0?-1:1;
}
struct Point
{
double x,y;
}p[55];
double disPP(Point a,Point b)
{
double dx=a.x-b.x;
double dy=a.y-b.y;
return sqrt(dx*dx+dy*dy);
}
const double INF=1000000000.0*100000000.0;
double dp[55][5*110];
int main()
{
freopen("4511.txt", "r", stdin);
int n,m;
int i,j,k;
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==0&&m==0)break;
for(i=1;i<=n;i++)
{
scanf("%lf%lf",&p[i].x,&p[i].y);
}
tot=-1;
clr();
while(m--)
{
ins();
}
get_fail();
for(i=0;i<=n;i++)
{
for(j=0;j<=tot;j++)
{
dp[i][j]=INF;
}
}
int h=node[0].next[0];
if(node[h].id>0)
{
puts("Can not be reached!");
continue;
}
dp[1][h]=0;
double cost=0;
for(i=1;i<=n;i++)
{
for(j=0;j<=tot;j++)
{
if(dblcmp(dp[i][j]-INF)==0)continue;
for(k=i+1;k<=n;k++)
{
h=node[j].next[k-1];
if(node[h].id)continue;
cost=disPP(p[i],p[k])+dp[i][j];
if(cost<dp[k][h])dp[k][h]=cost;
}
}
}
double ans=INF;
for(i=0;i<=tot;i++)
{
if(dp[n][i]<ans)ans=dp[n][i];
}
if(dblcmp(INF-ans)!=0)printf("%.2f\n",ans);
else puts("Can not be reached!");
}
return 0;
}
检讨:当时做这题的时候没有想到应该用ac自动机。换做当时经常做acm的时候应该能想到。
把题目给的非法路径构成一棵Trie树,然后做跑一次AC自动机。
这题就是一些基础的东西结合。算法本身不难。自己实现一下吧,不套模板。
这份代码用的静态trie较高效。