原题链接
P1039
题目类型:
普
及
+
/
提
高
{\color{yellow} 普及+/提高}
普及+/提高
AC记录:Accepted
题目大意
给出一个字符串和一个字典,要求你把这个字符串分成
k
k
k份,使每一份里面包含的字典里的单词数总和最多。注意:单词之间可以重叠,但开头不能使用同一个位置,即每一个位置只能有一个单词以他为开头。如this
中如果选了this
这个单词,则可以再选is
这个单词,但不能再选th
。
输入格式
第一行有二个正整数
p
,
k
p,k
p,k ,
p
p
p表示字串的行数,
k
k
k表示分为
k
k
k个部分。
接下来的
p
p
p行,每行均有
20
20
20个字符。代表字符串的第
(
p
−
1
)
×
20
+
1
(p-1)\times 20+1
(p−1)×20+1个字符到第
p
×
20
p\times 20
p×20 个字符。
再接下来有一个正整数
s
s
s,表示字典中单词个数。
接下来的
s
s
s行,每行均有一个单词。
输出格式
输出唯一的一个整数,为最长的演讲总时间。
S a m p l e \mathbf{Sample} Sample I n p u t \mathbf{Input} Input
1 3
thisisabookyouareaoh
4
is
a
ok
sab
S a m p l e \mathbf{Sample} Sample O u t p u t \mathbf{Output} Output
7
H
i
n
t
&
E
x
p
l
a
i
n
\mathbf{Hint\&Explain}
Hint&Explain
字符串分成this / isabookyoua / reaoh
。
第一段有一个is
。
第二段从左到右分别为is,sab,a,ok,a
。
第三段有一个a
。
总共7
个单词。
数据范围
对于 100 % 100\% 100%的数据, 1 ≤ s ≤ 6 , 1 ≤ p ≤ 10 , 1 < k ≤ 40 1≤s≤6,1\le p\le 10,1<k\le 40 1≤s≤6,1≤p≤10,1<k≤40。
解题思路
d
p
dp
dp,就是
d
p
dp
dp。
按本题的要求,我们首先要实现一个
w
o
r
k
work
work函数,代表在目标的字符串中有多少个单词。我在这里用的是直接用二维数组推一遍,当做初始化提前把
w
o
r
k
work
work函数执行了。
作者这里使用的是倒推。
我们想,每次插入一个字母,如果在当前位置上不能构成字母,那和不加这个字母有什么区别呢?如果在当前位置上能构成字母,也是在不插入这个字母的基础上单词量
+
1
+1
+1,于是,我们可以先设
w
o
r
k
i
,
j
work_{i,j}
worki,j为第
i
i
i位到第
j
j
j位所包含的单词数,初始时把他赋值为
w
o
r
k
i
+
1
,
j
work_{i+1,j}
worki+1,j,再判断他可不可以构成单词,如果可以,
w
o
r
k
i
,
j
work_{i,j}
worki,j就
+
1
+1
+1。
核心代码:
for(int j=s.size(); j>=1; j--)
for(int i=j; i>=1; i--)
{
word_num[i][j]=word_num[i+1][j];
string temp=s.substr(i,j-i+1);
for(int k=1; k<=num; k++)
{
if(temp.find(word[k])==0)
{
word_num[i][j]++;
break;
}
}
// cout<<"word_num["<<i<<"]["<<j<<"] = "<<word_num[i][j]<<endl;
}
d
p
dp
dp部分:
和P1018 [NOIP2000 提高组] 乘积最大很像,可以设
f
i
,
j
f_{i,j}
fi,j为前
i
i
i个字符分成
j
j
j段可以得到的最大单词数,再依次枚举第
j
j
j段的起点进行状态转移,就可以了。
状态转移方程:
f
i
,
j
=
{
w
o
r
k
1
,
i
j
=
1
f
i
−
1
,
j
−
1
+
w
o
r
k
i
,
j
i
=
j
max
j
≤
k
≤
i
{
f
k
−
1
,
j
−
1
+
w
o
r
k
k
,
i
}
1
≤
i
≤
p
×
20
,
1
≤
j
≤
min
(
k
,
i
)
f_{i,j}=\begin{cases} work_{1,i} & j=1 \\ f_{i-1,j-1}+work_{i,j} & i=j \\ \max_{j\le k\le i}\{f_{k-1,j-1}+work_{k,i}\} & 1\le i\le p\times 20,1\le j\le\min(k,i) \end{cases}
fi,j=⎩⎪⎨⎪⎧work1,ifi−1,j−1+worki,jmaxj≤k≤i{fk−1,j−1+workk,i}j=1i=j1≤i≤p×20,1≤j≤min(k,i)
而最后的答案为
f
p
×
20
,
k
f_{p\times 20,k}
fp×20,k。
第一条转移方程是当只分
1
1
1份的时候,那肯定全选,所以字母量为
w
o
r
k
1
,
i
work_{1,i}
work1,i。
第二条转移方程是当份数和字母数相同,即每一份只有一个字母,所以直接从上一个状态
f
i
−
1
,
j
−
1
f_{i-1,j-1}
fi−1,j−1加上当前的价值
w
o
r
k
i
,
j
work_{i,j}
worki,j就可以了。
第三条转移方程是枚举开头
k
k
k,其他的也不用多说了。
注意事项:
1.由于 w o r k work work函数使用的是倒推,所以循环要从大到小。
2.一个字母只能对应一个单词的开头,所以一旦找到单词符合,直接break
。
3.在第三条转移方程中, j j j循环的上限为 min ( k , i ) \min(k,i) min(k,i),是因为执行的过程中可能份数过多,字母不够分。
最后,祝大家早日
上代码
#include<iostream>
#include<cstring>
using namespace std;
string word[16];
int num;
int word_num[210][210];
int f[210][50];
int n,m;
void work()
{
cin>>n>>m;
memset(f,0,sizeof(f));
string s=" ";
for(int i=1; i<=n; i++)
{
string temp;
cin>>temp;
s+=temp;
}
cin>>num;
for(int i=1; i<=num; i++)
cin>>word[i];
for(int j=s.size(); j>=1; j--)
for(int i=j; i>=1; i--)
{
word_num[i][j]=word_num[i+1][j];
string temp=s.substr(i,j-i+1);
for(int k=1; k<=num; k++)
{
if(temp.find(word[k])==0)
{
word_num[i][j]++;
break;
}
}
// cout<<"word_num["<<i<<"]["<<j<<"] = "<<word_num[i][j]<<endl;
}
// for(int i=1; i<=n*20; i++)
// for(int j=i; j<=n*20; j++)
// cout<<"word_num["<<i<<"]["<<j<<"] = "<<word_num[i][j]<<endl;
for(int i=1; i<=m; i++)
f[i][i]=f[i-1][i-1]+word_num[i][i];
for(int i=1; i<s.size(); i++)
f[i][1]=word_num[1][i];
for(int i=1; i<s.size(); i++)
for(int j=1; j<=m&&j<i; j++)
for(int k=j; k<=i; k++)
f[i][j]=max(f[i][j],f[k-1][j-1]+word_num[k][i]);
cout<<f[s.size()-1][m]<<endl;
// for(int i=1; i<=n*20; i++)
// for(int j=0; j<=m; j++)
// cout<<"f["<<i<<"]["<<j<<"] = "<<f[i][j]<<endl;
return;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
// int t;
// cin>>t;
// while(t--)
work();
return 0;
}
完美切题 ∼ \sim ∼