HDU4511

本来想水一水AC自动机的,结果调了半天才调出来QAQ。
题意大概是有n个点,求1到n的最短路,且一些路径不能作为子串出现。
解法是把约束建一个AC自动机,在自动机上DP,由于是包含某个子串即非法,so构造fail往上跳时,如果当前和目标有一个为非法,另一个也应变成非法(见下图):这里写图片描述
这里x号节点已经是非法,所以y号节点也会变为非法,即程序中。这里写图片描述
然后DP时用f[i][j]记录原图第i个点在自动机上第j个节点的最短路径长度,每次对于当前i,j枚举下一个可以到达的点k,j进行更新。
最后ans=min(dp[n][0..节点数])
详细见代码:

#include <cstdio>
#include <algorithm>
#include <string>
#include <queue>
#include <cmath>
#include <cstring>
#define maxn 550
#define INF 0x7fffffffff
using namespace std;
int sz,n,m,k,ch[maxn][maxn],val[maxn],fail[maxn];
//           AC自动机       是否可以走 
string tmp;
double x[maxn],y[maxn],f[maxn][maxn];
//                     表示第i个点在自动机第j个节点的路径长度 
queue <int> q;

double dis(int a,int b)
{
    return sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]));
}

void insert()//插入约束字符串 
{
    int u=0,n=tmp.length();
    for (int i=0;i<n;i++)
    {
        int c=tmp[i];
        if (!ch[u][c])
        {
            memset(ch[sz],0,sizeof(ch[sz]));
            ch[u][c]=sz;
            val[sz++]=0;
        }
        u=ch[u][c];
    }
    val[u]=1;
}

void get_fail()
{
    for (int i=1;i<=n;i++)
      if (ch[0][i])q.push(ch[0][i]);
    while (!q.empty())
    {
        int u=q.front();q.pop();
        for (int i=1;i<=n;i++)
          if (ch[u][i])
          {
            int k;
            for (k=fail[u];k&&!ch[k][i];k=fail[k]);
            q.push(ch[u][i]);
            fail[ch[u][i]]=ch[k][i];
            val[ch[u][i]] | = val[ch[k][i]];//这一句很重要!很重要!!很重要!!!  
          }
          else ch[u][i]=ch[fail[u]][i];//注释2:这一句等价于注释3 
    }
}

int main()
{
    freopen("data.out","r",stdin);
    freopen("wa.out","w",stdout);
    while (scanf("%d%d",&n,&m))
    {
        memset(ch[0],0,sizeof(ch[0]));
        memset(fail,0,sizeof(fail));
        sz=1;
        if (n==0&&m==0) break;
        for (int i=1;i<=n;i++) scanf("%lf%lf",&x[i],&y[i]);
        for (int i=1;i<=m;i++)
        {
            scanf("%d",&k);
            tmp="";
            int p;
            for (int j=1;j<=k;j++) scanf("%d",&p),tmp+=p;
            insert();
        }
        get_fail();
        if (val[ch[0][1]])printf("Can not be reached!\n");//1作为起点不能到达无解 
        else
        {   
            for (int i=1;i<=n;i++)
              for (int j=0;j<sz;j++)
                f[i][j]=INF;
            f[1][ch[0][1]]=0;
            for (int i=1;i<n;i++)
              for (int j=0;j<sz;j++)
              {
                if (f[i][j]==INF) continue;
                for (int k=i+1;k<=n;k++)
                {
                    int jj=j;
                    while (jj&&!ch[jj][k]) jj=fail[jj];//注释3:等价于注释2 
                    jj=ch[jj][k];
                  if (!val[jj]) f[k][jj]=min(f[k][jj],f[i][j]+dis(k,i));//合法则更新 
                }
              }
              double ans=INF;
              for (int i=0;i<sz;i++)ans=min(ans,f[n][i]);
              if (ans==INF) printf("Can not be reached!\n");
              else printf("%.2f\n",ans);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值