状压DP实现顺序枚举 _ POJ 2817

题目链接

POJ 2817

题目意思

有n个字符串,可以以任何一种顺序排列,每相邻两个字符串之间的最长公共子串长度为这两个之间的分数,在每个字符串的前面可以添加任意数量的空格,那么请问最大得到分数是多少?

题目解析

首先我们看到一个关键字:相邻字符串之间最长公共子串,那么这里就避免不了求解这个东西,我们发现这里n的范围(1 <= n <= 10),并且字符串的长度也不会超过10,那么我们直接枚举来求解这个长度就好,时间复杂度为O(n^3),然后我们发现这个问题就变成了怎么排列n个字符串能使得到的分数最大,首先我们第一个想到的一定是全排列枚举每一种状态计算,这里我们就要用到生成全排列的函数next_permutation()来求解,但是我门还可以使用状压DP来实现这个功能

dp[i][j]:当前状态为i时,最后一个选取的字符串为j时的最优解

len[i][j]:字符串i与字符串j的最长公共子序列

实现过程:模拟一下这个过程

我们现在现在的状态中找到一个已经存在的1,假设这个1就是最后加入的字符串,然后我们再从里面找到一个状态为0(没有加入的字符串)的字符串,将这个状态为0的放到这个最后加入的字符串后面(上面找到状态为1的字符串),最终我门就可以得到最终的答案

数据:

3
abc
bcd
cde

我们得到len[i][j]数组,表示i与j之间最大的公共长度:len[1][2] = 2 ; len[2][3] = 2 ; len[1][3] = 1;

状态状态转移
000
001dp[3][2] = len[1][2] = 2,dp[5][3] = len[1][3] = 1
010

dp[3][1] = len[2][1] = 2,  dp[6][3] = len[2][3] = 2

011dp[7][3] = dp[3][1] + len[1][3] = 3,dp[7][3] = dp[3][2] + len[2][3] = 4 
100dp[5][1] = len[3][1] = 1 , dp[6][2] = len[3][2] = 2
101dp[7][2] = dp[5][1]+len[1][2] = 3 , dp[7][2] = dp[5][3] + len[3][2] = 3 
110dp[7][1] = dp[6][2] + len[2][1] = 4, dp[7][3] = dp[6][3] + len[3][1] = 3
111

从上面的状态转移看一看到也就是我们假设每一次第一个找到的1为最后一个当前顺序中的最后一个,那么我们再去寻找一个0,将这个状态放到当前1的后面,用前面的状态实现了当前状态的计算

程序实现

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define INF 0x3f3f3f3f

using namespace std;
const int N = 11;
int n;
char str[N][N];
int len[N][N];
int dp[1<<N][N];

int Get_LSA(int x,int y)
{
    int ans = 0;
    int len_x = strlen(str[x]);
    int len_y = strlen(str[y]);
    for(int i = 0;i < len_x;i ++)
        for(int j = 0;j < len_y;j ++)
        {
            int num = 0,sum = 0;
            while(num+i < len_x && num+j <len_y)
            {
                if(str[x][num+i] == str[y][num+j]) sum++;
                num++;
            }
            ans = max(ans,sum);
        }
    return ans;
}
int main()
{
    while(~scanf("%d",&n) && n)
    {
        memset(len,0,sizeof(len));
        for(int i = 0;i < n;i ++)
            scanf("%s",str[i]);
        for(int i = 0;i < n;i ++)
            for(int j = i+1;j < n;j ++)
                len[i][j] = len[j][i] = Get_LSA(i,j);//得到每一个字符串之间最长的公共子串

        //求出最长公共子串之后就是一个排序的问题!
        //枚举出一个选择的字符串和一个未选择的字符串,通过两个字符串的拼接更新拼接之后的状态
        memset(dp,0,sizeof(dp));//dp[i][j]:当前选取的字符串为i状态,且最后一个选取的字符串是第j个字符串的最优值
        for(int i = 0;i < (1<<n);i ++)//一共有(1<<n)
            for(int j = 0;j < n;j ++)
            {
                if(i & (1<<j))//首先找到一个为1的位置,设置为当前状态最后一个放入的字符串
                {
                    for(int k = 0;k < n;k ++)
                    {
                        if(!(i &(1 << k)))//将这个为0的状态添加到里面,添加到这个1的后面,将这个变成现在状态下最后一个放入的字符串
                        {
                            dp[i|(1<<k)][k] = max(dp[i|(1<<k)][k],dp[i][j] + len[j][k]);
                        }
                    }
                }
            }
        int ans = -1;
        for(int i =0 ;i < n;i ++)
            ans = max(ans,dp[(1<<n)-1][i]);
        printf("%d\n",ans);
    }
    return 0;
}

Another栗子   

D. Kefa and Dishes

这个题里面和上面的题很是类似,只不过这个里面每一个都会有一个初始的数值,所以我们的dp数组就会有一步初始化的过程,并且我们这个里面不光是n个中挑选m个来进行求解最大值,所以当我们枚举的状态个数为m个的时候我们就要更新最大的数值,我们为什么不能只更新到m呢?因为我们这里更新的顺序不是从小到大的顺序,所以后面还会有大小为m的时候,我们这里要说明一点:虽然我们枚举的状态里面比如100100这个状态他的3,6不是连在一起的,但是我们在程序中他们就是连在一起的,并且记录了这两个哪个在前面哪个在后面的

#include <iostream>
#include <algorithm>
#include <queue>
#include <cstdio>
#include <cmath>
#include <cstring>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
const int N = 19;
ll dp[1<<N][N];
int n,m,k;
int val[N];
int G[N][N];
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i = 0;i < n;i ++)
        scanf("%d",&val[i]);
    int u,v,w;
    memset(G,0,sizeof(G));
    for(int i =0 ;i < k;i ++)
    {
        scanf("%d%d%d",&u,&v,&w);
        G[u-1][v-1] = w;
    }
    memset(dp,0,sizeof(dp));
    for(int i = 0;i < n;i ++)//进行数值初始化
        dp[1<<i][i] = val[i];
    ll ans = 0;
    for(int i =0 ;i < (1<<n);i ++)
    {
        int cnt = 0;
        for(int j = 0;j < n;j ++)
        {
            if(i & (1<<j))//当前最后一个位置为j
            {
                cnt++;
                for(int k = 0;k < n;k ++)
                    if(!(i & (1<<k)))
                    {
                        dp[i|(1<<k)][k] = max(dp[i|(1<<k)][k],dp[i][j] + G[j][k] + val[k]);
                    }
            }
        }
        if(cnt == m)
        {
            for(int k = 0;k < n;k ++)
                if(i & (1<<k))
                    ans = max(ans,dp[i][k]);
        }
    }
    printf("%I64d\n",ans);
}

参考博客

http://www.cnblogs.com/ACMan/archive/2012/08/08/2628853.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值