现在的位置: 首页 > 综合 > 正文

[poj 2817]Wordstack[状压DP]

2018年03月17日 ⁄ 综合 ⁄ 共 3127字 ⁄ 字号 评论关闭

题意:

给出N个串, 将其排列在N行上, 利用空格缩进, 使得每一行和上面一行的串重叠的部分最长. 求这个长度.

思路:

N最大为10, 考虑状压dp.

state为串的状态, dp[state][ i ] 表示state状态下以第 i 个串开头的方案中,重叠的最大长度. 则有:

枚举state中的任意一个串 i 作为第一行, 另一个串 j 作为第二行, state' = state^(1<<i);(去掉第 i 个串)

dp[state][ i ] = dp[state' ][ j ] + cat[ i ][ j ] or cat[ j ][ i ];

其中, cat[ i ][ j ]表示 i 和 j 重叠的最大长度...暴力一下

题意"重叠"理解有误了....不一定要连续.....只要对上就行

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 12;
const int INF = 0x3f3f3f3f;
int next[MAXN],dp[1<<10][MAXN],cat[MAXN][MAXN],n;
char str[MAXN][MAXN],dis[MAXN];
//fill cat[i][j]...bruce
int cmp(char a[], int la, char b[], int lb)
{
    int ans = 0;
    int pa = la - 1, pb = 0;
    for(;pa>=0;pa--)
    {
        int cnt = 0;
        for(int i=0;i<la-pa && i<lb-pb;i++)
            if(a[pa+i]==b[pb+i])    cnt++;
        ans = max(ans, cnt);
    }
    pa = 0;
    for(;pb<lb;pb++)
    {
        int cnt = 0;
        for(int i=0;i<la-pa && i<lb-pb;i++)
            if(a[pa+i]==b[pb+i])    cnt++;
        ans = max(ans, cnt);
    }
    return ans;
}
void pre()
{
    for(int i=0;i<n;i++)
        for(int j=i+1;j<n;j++)
            cat[i][j] = cat[j][i] = cmp(str[i],strlen(str[i]),str[j],strlen(str[j]));
}
/*
char* bin(int x)
{
    dis[n] = '\0';
    for(int i=0;i<n;i++)
    {
        if((1<<i)&x)    dis[n-i-1] = '1';
        else            dis[n-i-1] = '0';
    }
    return dis;
}*/
int dfs(int s, int i)
{
    if(dp[s][i]!=INF)   return dp[s][i];
    if(!s)  return dp[s][i] = 0;
    int cnt = __builtin_popcount(s);
    if(cnt==1)  return dp[s][i] = 0;
    int ans = 0;
    for(int j=0;j<n;j++)
        if(i!=j && (1<<j)&s)
            ans = max(ans, dfs(s^(1<<i),j)+cat[i][j]);
            //printf("dp[%s][%d] = %d\n",bin(s^(1<<i)),j,dp[s^(1<<i)][j]);
    return dp[s][i] = ans;
}
int main()
{
    while(scanf("%d",&n)==1 && n>0)
    {
        for(int i=0;i<n;i++)
            scanf("%s",str[i]);
        pre();
        int ans = 0;
        memset(dp,0x3f,sizeof(dp));
        for(int i=0;i<n;i++)
            ans = max(ans, dfs((1<<n)-1, i));
           // printf("dp[%s][%d] = %d\n",bin((1<<n)-1),i,dp[(1<<n)-1][i]);
        printf("%d\n",ans);
    }
}

除了题意理解有误之外...

这是很久以来第一次自己想出算法的一题TAT....

看了下题解...有更好的dp方式...不需要dfs...直接填表

就是定义dp[ state ][ i ]为state状态下最后一个选取的串为 i 时, 重叠的最大字符数.

转移的时候并不是去掉(这样的话就又成dfs了)而是加入...

看原文吧:

状态转移:

枚举某个状态时,枚举一个已选的字符串(即当前状态二进制位为1的位),再枚举一个未选的字符串(当前状态二进制位为0的位),通过这两个字符串的拼接来更新拼接之后新的状态,因为加进了一个没在状态中的字符串,所以状态变成了i|(1<<k)  假设i是当前枚举的状态,k是二进制位为0的位
所以状态转移就为
               dp[i|(1<<k)][k]=max(dp[i|(1<<k)][k],dp[i][j]+len[j][k]);
如果大家仔细观察一下代码中的关键转移部分,会发现:当我们要去更新dp[i|(1<<k)][k]状态时,dp[i][j]肯定已经是求好了的,在这道题目里dp[i][j]就是dp[i|(1<<k)][k]的子结构,每次都尝试着用dp[i|(1<<k)][k]的子结构去更新它.

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 12;
const int INF = 0x3f3f3f3f;
int next[MAXN],dp[1<<10][MAXN],cat[MAXN][MAXN],n;
char str[MAXN][MAXN],dis[MAXN];
//fill cat[i][j]...bruce
int cmp(char a[], int la, char b[], int lb)
{
    int ans = 0;
    int pa = la - 1, pb = 0;
    for(;pa>=0;pa--)
    {
        int cnt = 0;
        for(int i=0;i<la-pa && i<lb-pb;i++)
            if(a[pa+i]==b[pb+i])    cnt++;
        ans = max(ans, cnt);
    }
    pa = 0;
    for(;pb<lb;pb++)
    {
        int cnt = 0;
        for(int i=0;i<la-pa && i<lb-pb;i++)
            if(a[pa+i]==b[pb+i])    cnt++;
        ans = max(ans, cnt);
    }
    return ans;
}
void pre()
{
    for(int i=0;i<n;i++)
        for(int j=i+1;j<n;j++)
            cat[i][j] = cat[j][i] = cmp(str[i],strlen(str[i]),str[j],strlen(str[j]));
}
int solve()
{
    memset(dp[0],0,sizeof(dp[0]));
    memset(dp[1],0,sizeof(dp[1]));
    for(int state=1;state<1<<n;state++)
        for(int i=0;i<n;i++)
            if((1<<i)&state)
                for(int j=0;j<n;j++)
                    if((1<<j)&(~state))
                        dp[state|(1<<j)][j] = max(dp[state|(1<<j)][j], dp[state][i]+cat[i][j]);
    int ans = 0;
    for(int i=0;i<n;i++)
        ans = max(ans, dp[(1<<n)-1][i]);
    return ans;
}
int main()
{
    while(scanf("%d",&n)==1 && n>0)
    {
        for(int i=0;i<n;i++)
            scanf("%s",str[i]);
        pre();
        int ans = 0;
        memset(dp,0,sizeof(dp));
        printf("%d\n",solve());
    }
}


抱歉!评论已关闭.