[BZOJ1195] [HNOI2006]最短母串(状压dp)

题目描述

传送门

题解

f[i][j]表示状态为j,最后一个子串是i的最短的字符串的长度。
不过这里有一个问题,就是用来转移的所有字符串不能存在包含关系。也就是说,字符串只能存在部分相交或相离。这样的话才能保证每一次都是部分接在了前一个的最后一个字符串后面,从而保证了dp的无后效性。部分包含的循序是不要紧的,因为总会有从正确的顺序转移过来的。
字典序的话就记一下转移的前驱然后暴力还原判断辣,中间转移和最后统计答案的时候都要考虑字典序的影响。其实写起来不算很简单。

代码

#include
#include
#include
#include
#include
using namespace std;

int n,tot,ans,ansi,inf;
struct hp
{
    char s[100];
    int len,exlen,prei,prej;
}s[20],str[20],f[20][5000];
char s1[1000],s2[1000]; int cnt1,cnt2;
bool vis[20][5000];
struct sit{int i,j;};
queue  q;

int cmp(hp a,hp b)
{
    return a.len>b.len;
}
bool included(int a,int b)
{
    for (int i=0;ilen;++i)
        if (s[a].s[i]==s[b].s[0])
        {
            bool flag=true;
            for (int j=0;jlen;++j)
                if (s[a].s[i+j]!=s[b].s[j])
                {
                    flag=false;
                    break;
                }
            if (flag) return true;
        }
    return false;
}
int connext(int a,int b)
{
    for (int i=0;ilen;++i)
        if (str[a].s[i]==str[b].s[0])
        {
            bool flag=true;
            for (int j=i;jlen;++j)
                if (str[a].s[j]!=str[b].s[j-i])
                {
                    flag=false;
                    break;
                }
            if (flag) return str[b].len-(str[a].len-i);
        }
    return str[b].len;
}
void pre(char *s,int &cnt,int i,int j)
{
    if (f[i][j].prei!=inf||f[i][j].prej!=inf) pre(s,cnt,f[i][j].prei,f[i][j].prej);
    for (int k=str[i].len-f[i][j].exlen;klen;++k)
        s[cnt++]=str[i].s[k];
}
bool small(int i1,int j1,int i2,int j2)
{
    memset(s1,0,sizeof(s1)),cnt1=0;
    pre(s1,cnt1,i1,j1);
    memset(s2,0,sizeof(s2)),cnt2=0;
    pre(s2,cnt2,i2,j2);

    for (int i=0;iif (s1[i]false;
        else if (s1[i]>s2[i]) return true;
    return false;
}
int main()
{
    scanf("%d\n",&n);
    for (int i=1;i<=n;++i) gets(s[i].s),s[i].len=strlen(s[i].s);
    sort(s+1,s+n+1,cmp);
    for (int i=1;i<=n;++i)
        for (int j=i+1;j<=n;++j)
            if (s[i].len&&s[j].len)
                if (included(i,j)) s[j].len=0;
    for (int i=1;i<=n;++i)
        if (s[i].len) str[++tot]=s[i];

    memset(f,127,sizeof(f));inf=f[0][0].len;
    for (int i=1;i<=tot;++i)
    {
        f[i][1<<(i-1)].len=f[i][1<<(i-1)].exlen=str[i].len;
        q.push((sit){i,1<<(i-1)});
    }

    while (!q.empty())
    {
        sit now=q.front(); q.pop();
        vis[now.i][now.j]=false;
        for (int i=1;i<=tot;++i)
            if ((now.j>>(i-1)&1)!=1)
            {
                int nxt=connext(now.i,i);
                if (f[now.i][now.j].len+nxtnow.j|(1<<(i-1))].len)
                {
                    f[i][now.j|(1<<(i-1))].len=f[now.i][now.j].len+nxt;
                    f[i][now.j|(1<<(i-1))].exlen=nxt;
                    f[i][now.j|(1<<(i-1))].prei=now.i;
                    f[i][now.j|(1<<(i-1))].prej=now.j;
                    if (!vis[i][now.j|(1<<(i-1))])
                    {
                        vis[i][now.j|(1<<(i-1))]=true;
                        q.push((sit){i,now.j|(1<<(i-1))});
                    }
                }
                else if (f[now.i][now.j].len+nxt==f[i][now.j|(1<<(i-1))].len&&small(f[i][now.j|(1<<(i-1))].prei,f[i][now.j|(1<<(i-1))].prej,now.i,now.j))
                {
                    f[i][now.j|(1<<(i-1))].exlen=nxt;
                    f[i][now.j|(1<<(i-1))].prei=now.i;
                    f[i][now.j|(1<<(i-1))].prej=now.j;
                    if (!vis[i][now.j|(1<<(i-1))])
                    {
                        vis[i][now.j|(1<<(i-1))]=true;
                        q.push((sit){i,now.j|(1<<(i-1))});
                    }
                }
            }
    }
    ans=inf;
    for (int i=1;i<=tot;++i)
        if (f[i][(1<1].len1<1].len;
            ansi=i;
        }
        else if (f[i][(1<1].len==ans)
        {
            if (small(ansi,(1<1,i,(1<1))
                ansi=i;
        }
    memset(s1,0,sizeof(s1)),cnt1=0;
    pre(s1,cnt1,ansi,(1<1);
    for (int i=0;i'\n');
}

总结

①字符串下标从0开始,最好都统一。
②判断函数的顺序不要搞反了。

你可能感兴趣的:(题解,dp,省选)