UVA - 1204 Fun Game状压dp

题目

一个环,给定n(n<=16)个字符串,都是环上的字串,求原始的环最小是多长

分析

这道题感觉挺麻烦的,关键是给定字符串的顺序可以是顺时针的,也可以是逆时针,然后还要确定起始位置,最后连成环的时候(就是首尾相接时),如何保证环是最小的
开始时我理解错了,认为给了BGGB和BGG拼的是出来的BGG 而不是BGGBGG,读了代码后确定了所有的字符串都是环的子串,不存在循环的可能

首先预处理,将是其他字符串子串的字符串删除,这里有很多小技巧,感觉很简洁,可以参考刘汝佳的代码。
然后因为是个环,将[0][0] 第0字符串正序作为起始 dp[1][0][0]=len[0],用刷表法更新状态
搜索结尾的字符串,确定结果

首先将字符串拼接成一条线
状态 dp[s][j][k] s是一个集合,代表已经被连接的字符串集合,j是连接的最后一个字符串,k=0 or 1代表j的顺序,dp[s][j][k]代表s个字符串重叠起来长度
最后将j(j需要比较才能确定)和0相连,减去覆盖的部分,就可以连接成环
状态转移方程:对于现有的状态s,接尾是字符串j,顺序k,可以向他右边继续添加字符串i,i的顺序是l,转移方程如下
d p [ s ( 1 < < i ) ] [ i ] [ l ] = d p [ s ] [ j ] [ k ] + o v e r l a p [ j ] [ i ] [ k ] [ l ] dp[s|(1<<i)][i][l]=dp[s][j][k]+overlap[j][i][k][l]

最后,如果结果是1,要输出2,因为题目要求

总结

还是对于状压dp不够熟悉,这题使用状压dp还是很明显的,问题是怎么表示字符串顺序(增加一个维度表示最后一个字符串的顺序),本题中运用了一些技巧简化计算,值得注意

//
// Created by Zhao Pengfei on 2019/11/14.
//
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <utility>
using namespace std;
const int maxn=16;
struct Item{
    string str,rev;
    bool operator < (const Item &rhs) const {
        return str.length()<rhs.str.length();
    }
};
//overlap[i][j][k][l]中,i,j代表两个字符串的序号,k代表i的顺序,l代表j的顺序
//dp[s][j][k]
int n,m,overlap[maxn][maxn][2][2],dp[1<<16][maxn][2],len[maxn];
Item items[maxn];
string s[maxn][2];   //小技巧,这样分成二维表示顺序,方便传入Overlap函数中计算,不用写if判断顺序

//a(left),b(right) overlap length
int Overlap(string &a,string &b){
    int len1=a.length();
    int len2=b.length();
    for(int i=0;i<len1;++i){
        if(i+len2<=len1) continue; //没有到能够重合的位置
        bool flag=true;
        for(int j=0;i+j<len1;++j){
            if(a[i+j]!=b[j]){
                flag=false;
                break;
            }
        }
        if(flag){
            return len1-i;
        }
    }
    return 0;
}

void Init(){
    //输入,预处理,删除被包含的子字符串
    for(int i=0;i<n;++i){
        cin>>items[i].str;
        items[i].rev=items[i].str;
        reverse(items[i].rev.begin(),items[i].rev.end());
    }
    sort(items,items+n);
    m=0;
    for(int i=0;i<n;++i){
        bool flag=true;
        for(int j=i+1;j<n;++j){
            if(items[j].str.find(items[i].str)!=string::npos
                ||items[j].str.find(items[i].rev)!=string::npos ) {
                flag = false;
                break;
            }
        }
        if(flag){
            s[m][0]=items[i].str;
            s[m][1]=items[i].rev;
            len[m]=s[m][0].length();
            m++;
        }
    }

    for(int i=0;i<m;++i){
        for(int j=0;j<m;++j){
            for(int k=0;k<2;++k){
                for(int l=0;l<2;++l){
                    overlap[i][j][k][l]=Overlap(s[i][k],s[j][l]);
                }
            }
        }
    }
}

void update(int &x,int y){
    if(x==-1 || y<x)  x=y;
}

//这里使用刷表法进行状态转移
void Solve(){
    memset(dp,-1,(1<<m)*sizeof(dp[0]));
    dp[1][0][0]=len[0];
    int S=(1<<m);
    //因为是刷表法,s可以小于S-1
    for(int s=0;s<S-1;++s){
        for(int j=0;j<m;++j){
            for(int k=0;k<2;++k){
                if(dp[s][j][k]<0) continue;
                int t=dp[s][j][k];
                for(int i=0;i<m;++i){   //选择下一个字符串
                    if(s&(1<<i)) continue;
                    update(dp[s|(1<<i)][i][0],t+len[i]-overlap[j][i][k][0]);
                    update(dp[s|(1<<i)][i][1],t+len[i]-overlap[j][i][k][1]);
                }
            }
        }
    }
    int ans=-1;
    for(int i=0;i<m;++i){
        for(int k=0;k<2;++k){
            if(dp[S-1][i][k]==-1) continue;
            update(ans,dp[S-1][i][k]-overlap[i][0][k][0]);
        }
    }
    if(ans<=2) printf("2\n");
    else printf("%d\n",ans);
}

int main(void){
    while(cin>>n && n){
        Init();
        Solve();
    }
}
发布了15 篇原创文章 · 获赞 0 · 访问量 174

猜你喜欢

转载自blog.csdn.net/zpf1998/article/details/103947024