Codeforces Round #764 (Div. 3)
题意
给出n个长度为m的字符串,然后给出一个长度为m的字符串 s ,将m分成若各干个长度不小于2的连续字段。问是否存在一种分法,使得这些连续字段在前面的n个字符串中均出现过。如果有多组解,输入任一;无解输出-1。
思路
- 任何长度大于3的字符串均可以分成长度为2和3的字段。把n个字符串中长度为2和3的子段位置以及这些子段所在字符串的下标存起来(
map<string, tuple<int, int, int>>
)。 pre[N]
:记录以第i个字符结尾的子段的起始位置dp[N]
:记录s是否可以拓展到第i位。初始dp[0] = 1
,判断dp[m] == 1
即可判断是否有解- 注意倒序取出有解的情况下s的子段信息。记得将这些信息的顺序调为正序(reverse)
注意
- substr(参数1,残数2)
第一个参数是起始位置,第二个是复制的字符串的长度 - tuple元组的用法:
tuple<类型1, 类型2, … , 类型n>;
get<下标>(元组名称);(下标从0开始)
代码
const int N = 1e6 + 7;
map<string, int>have;
map<string, tuple<int, int, int>>pos;
int dp[N], pre[N];
//dp数组表示字符串s该匹配第i + 1位了,前i位已经匹配
//pre的第i + 1位记录匹配到的以第i位结尾的子段起始位
int main()
{
IOS;
int T; cin >> T;
while (T--)
{
have.clear();
int n, m; cin >> n >> m;
for (int i = 0; i <= m + 1; i++)
dp[i] = 0;
for (int i = 0; i < n; i++)
{
string s; cin >> s;
for (int j = 0; j < m; j++)
{
string t;
t = s[j];
for (int k = 1; k <= 2; k++)
{
if (j + k >= m) break;//注意越界
t += s[j + k];
if (!have[t])
{
have[t] = 1;
pos[t] = make_tuple(j, j + k, i);
//记录长度为2和3的子段的位置即所在字串的编号
}
}
}
}
dp[0] = 1;//从0为开始
string s; cin >> s;
for (int i = 0; i < m; i++)
{
string t; t = s[i];
for (int j = 1; j <= 2; j++)
{
if (i - j < 0) break;
t = s[i - j] + t;
if (have[t] && dp[i - j])
dp[i + 1] = 1, pre[i + 1] = i - j;//
if (dp[i + 1]) break;
}
}
if (!dp[m])
{
cout << -1 << endl;
continue;
}
vector<tuple<int, int, int>>v;
for (int i = m; i > 0;)//注意是倒序
{
int p = pre[i];
string t;
//此处特备注意!!!
//substr第二个参数是复制的字符串的长度,
t = s.substr(p, i - p);
v.push_back(pos[t]);
i = p;
}
reverse(all(v));
int len = sz(v);
cout << v.size() << endl;
for (int i = 0; i < len; i++)
cout << get<0>(v[i]) + 1 << " " << get<1>(v[i]) + 1 << " " << get<2>(v[i]) + 1 << endl;
}
return 0;
}