100道动态规划——25 UVA 1204 Fun Game 状态压缩 圆圈的处理 (100道完成了1/4啦)

博客作者分享了在解决UVA 1204 Fun Game问题时使用动态规划和状态压缩的方法。已完成100道动态规划题目中的四分之一,紫书上的DP题目即将结束,接下来可能转向其他资源继续刷题。在解决问题过程中,作者发现简化问题并处理字符串的正反和重叠部分是关键,通过定义状态dp[i][j][x]表示当前状态,并计算不同字符串的重叠长度,实现了状态转移方程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        首先说一说,100道完成了1/4啦,虽然是值得稍稍庆祝一下的事情.......可是,紫书上的DP快写完了。。。以后去白书上刷题,然后白书刷完了我也不知道去哪儿刷了。。。

        说说这道题的想法,状态压缩。我发现以前在写状态压缩的总结的一条规律(数据上限小可以考虑状态压缩)并没有什么用。。。。首先要知道是一个DP呀,然后就算知道了是状态压缩也想不出来=_=

        我觉得紫书上说的非常的好,先处理一个简化版的问题,就是考虑某个字符串str使得这n个字符串皆为str的连续子串且str长度最短,然后可以扩展到考虑正反,加一维描述正反即可。至于原问题是圆圈,老实说这个我可能想不到,就是在做出答案之后减去最后一次的字符串和第一个字符串的重叠

        定义状态dp[i][j][x]表示当前拿的字符串状态为i,最后一次拿的字符串为j,j的正反状态为x的最少长度。

        预处理去掉所有的被其他串包含的串,然后处理出所有串的重叠长度。

        状态的转移就很好写了嘛,考虑当前一个没有拿到的串,假设为j,正反状态为y(j要求没有拿过,即i&(1<<j)为0),当前拿到的串的集合为s且当前最后一次的串为i,i的状态是x

        dp[s|(1<<j)][j][y]=dp[s][i][x]+length[j]-overlap[i][j][x][y]

        length[j]代表第j个字符串的长度,overlap[i][j][x][y]代表正反状态为x的第i字符串和正反状态为y的第j字符串的重叠长度(i左j右)

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int maxm=1<<17;

inline void update(int& x,int v){if(x<0||x>v)x=v;}
void init(),slove();
int overlap(const string& a,const string& b);
int dp[maxm+10][20][2],n,over[20][20][2][2],len[20];
string arr[20][2];

struct Node{
    string str,rstr;
    bool operator<(const Node& node)const{return str.size()<node.str.size();}
};

int main(){
    ios_base::sync_with_stdio(false);
    while(cin>>n&&n){
        init();
        slove();
    }
    return 0;
}

int overlap(const string& a,const string& b){
    int n1=a.size(),n2=b.size();
    bool flag=true;
    for(int i=1;i<n1;++i,flag=true)
    if(n2+i>n1){
        for(int j=0;j+i<n1;++j)
        if(a[i+j]!=b[j]){
            flag=false;
            break;
        }
        if(flag)
            return n1-i;
    }
    return 0;
}

void init(){
    int n2=0;
    Node node[20];
    for(int i=0;i<n;++i){
        cin>>node[i].str,node[i].rstr=node[i].str;
        reverse(node[i].rstr.begin(),node[i].rstr.end());
    }

    sort(node,node+n);

    for(int i=0,j;i<n;++i){
        for(j=i+1;j<n;++j)
        if(node[j].str.find(node[i].str)!=string::npos||node[j].str.find(node[i].rstr)!=string::npos)
            break;
        if(j==n){
            arr[n2][0]=node[i].str;
            arr[n2][1]=node[i].rstr;
            len[n2++]=node[i].str.size();
        }
    }
    n=n2;

    for(int i=0;i<n;++i)
    for(int j=0;j<n;++j)
    for(int k=0;k<2;++k)
    for(int l=0;l<2;++l)
        over[i][j][k][l]=overlap(arr[i][k],arr[j][l]);
}

void slove(){
    memset(dp,-1,sizeof dp);
    int limi=(1<<n)-1;

    dp[1][0][0]=len[0];
    for(int s=1;s<limi;++s)
    for(int i=0;i<n;++i)
    for(int x=0;x<2;++x)
    if(dp[s][i][x]>=0)
    for(int k=1;k<n;++k)
    if(!(s&1<<k))
    for(int y=0;y<2;++y)
        update(dp[s|(1<<k)][k][y],dp[s][i][x]+len[k]-over[i][k][x][y]);

    int ans=-1;
    for(int i=0;i<n;++i)
    for(int x=0;x<2;++x)
    if(dp[limi][i][x]>=0)
        update(ans,dp[limi][i][x]-over[i][0][x][0]);

    if(ans<=1)
        ans=2;
    cout<<ans<<endl;
}



        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值