题意:
给出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());
}
}