今天给大家带来的系列还是我们的状态压缩DP系列,相信根据前几天大家的学习和了解,已经对状态压缩DP有了一个基本的了解。
1.理解这类问题的关键是对理解dp数组的定义,我们常见的的定义方式就是dp[i][j],其中i是用二进制表示当前存在的状态,而j一般是根据题意选择当前满足题意的条件,(一般是以什么什么结尾)。
2.其次就是要写出相关的执行条件,如if(i&(1<<j)),这就一般表示当前当前i的状态与j相符,这样的目的其实是将确定i,而j的循环范围则一般是与输入的n相关。因为第三步状态转移方程的实现需要根据题意来转移,想要弄清楚这个转移过程,则必须得理解转移前后的关系和变化,选择当前情况对于之后的影响是什么,以此来确定转移条件。
问题描述
在一段文本中,如果一个单词的首字母与在其之前的某个单词的最后一个字母相等,我们称这个单词与之前的那个单词具有关联关系。如果在这段文本中,除了第一个单词,其他的单词都与前一个单词有关联关系,我们则称这个文本为关联文本。
现在有一个大小为 n 的字符串数组word ,你可以挑出其中任意个字符串以任意顺序组成一段关联文本(文本不需要遵守语法规则),请你输出组成的关联文本最多能拥有多少个字符串。
输入格式
输入数据有 2 行。
第一行有一个整数 n ,表示数组的大小。
接下来一行为 n 个字符串,表示数组的 n 个字符串,两个字符串之间由空格隔开。
输出格式
输出一个整数,表示关联文本最多能拥有的字符串个数。
#include <bits/stdc++.h>
using namespace std;
const int maxn=30;
const int maxx=2e6;
string word[maxn];
int first[maxn];//first[i]存储第i个字符串的首字母
int last[maxn];//last[i]存储第i个字符串的末尾字母
int dp[maxx][maxn];
//dp[i][j]表示从word[0]到word[i]区间内以字符j结尾的关联字符串的最大数量
int main()
{
int N;
cin>>N;
for(int i=0;i<N;i++)
{
cin>>word[i];
first[i]=word[i][0]-'a';//存储该字符串的首字符
last[i]=word[i][word[i].size()-1]-'a'; //存储该字符串的末尾字符
}
for(int i=0;i<(1<<N);i++)//枚举N个字符串(视为N位二进制数)的所有状态00..000~11...111
{
//若某位为0,代表不选择该字符串;若为1,代表选择该字符串
for(int j=0;j<N;j++)//枚举N位二进制数的每一位j,即是否能选择第j个字符串作为最末的字符串
{
if(i&(1<<j))//取当前二进制数的第j位,若为1,则其可以作为最后选择的字符串
{
//若选择该字符串,则上一个状态的关联字符串以first[j]结尾,
//由于i的第j位是1,故i^(1<<j)的第j位是0,代表上一个状态一定没选第j个字符串
//选择j以后关联字符串的数量加1,即为dp[i^(1<<j)][first[j]]+1
//若其比dp[i][last[j]]更大,则更新dp[i][last[j]]即可
dp[i][last[j]]=max(dp[i][last[j]],dp[i^(1<<j)][first[j]]+1);
}
}
}
int ans=0;
for(int i=0;i<26;i++)//枚举所有可能的末尾字符i
{
//dp[(1<<N)-1][i]即对于全部的字符串,以字符i结尾的关联字符串的最大数量
//对于不同的i,不断比较得出其中的最大值即为最终答案
ans=max(ans,dp[(1<<N)-1][i]);
}
cout<<ans<<endl;
return 0;
}
这道题对于字符串输入的定义和首字母和末字母的处理方式,大家也可以参照学习学习。
大家可以根据这道题再次加深对于状态压缩DP的了解和认识,这类问题的标志是n的范围一般为20-30,其次样解决好处就是忽略了具体的从1到n的循环,而是列出所有可能存在的情况i来实现算法的解决。
今天的分享就到这里,希望能够帮助大家。
最后希望大家多多关注和点赞。