【题解】String Bags 题解

\(\mathtt{Pre\ Explanation}\)

口胡出奇迹!STL 出奇迹!

本题解中 \(|s|\) 表示字符串 \(s\) 的长度。

\(\mathtt{Solution}\)

作为一位合格的 OIer,你第一眼看到本题的想法是:暴力或者贪心。暴力在 Atcoder 赛制中显然不可取,于是你考虑贪心。

你在想:贪心的策略应该是每组选择最长的那个?可是不行啊,万一你选了最长的,后面接不上了怎么办?很有可能选个短的,后面再选个短的就可以接上?

既然贪心不行,那么你就开始思考 DP。你参考 LIS 的定义方法,决定定义 \(f_i\) 为凑出 \(T\) 的前 \(i\) 位的最小代价。这个时候你又发现你需要保证每个组只能选一个,为了不重复选择,你想起了做 01 背包时的状态定义,选择增加一维,于是你把定义改成了 \(f(i,j)\) 代表用前 \(i\) 组的字符串凑出 \(T\) 的前 \(i\) 位的最小代价。

有了状态定义,你开始考虑转移。对于第 \(i\) 组,你发现可以啥也不做,于是你写下了 \(f(i,j)=f(i-1,j)\)。但是第 \(i\) 组还可以选择一个字符串转移,每个字符串都有转移的可能,于是你选择枚举这个组的所有字符串 \(s_k\)\(1 \le k \le c_i\))。显然,当一个字符串是 \(T\) 的后缀时可以转移。抛去这个后缀,前面的部分,也就是 \(T\) 的前 \(j-|s_k|\) 位,就由前 \(i - 1\) 组负责组成,这样才可以保证不重复,组成的代价就是 \(f(i-1,j-|s_k|)\),而接上这个后缀本身还有 \(1\) 点代价。于是你写下了下面这条式子:

\[f(i,j)=\min\{f(i,j),f(i-1,j-|s_k|) + 1\} \]

当然,你知道这个转移只有在 \(s_k\)\(T\) 的后缀时才可以进行。而你又考虑到 \(f(i,j)=f(i-1,j)\) 对于每个 \(j\) 都成立,因此你决定在转移前先复制一下 \(f(i-1)\)\(f(i)\),这样子就只用考虑第二种转移了。

现在你苦于怎么判断后缀,这个时候你突然想起了 substr,你只需要通过它取出最后 \(|s_k|\) 位,然后再来看看是否与 \(s_k\) 一样就可以了。

你开始敲代码,敲的时候发现如果 \(|s_k|>j\),那么显然转移不了,直接跳过即可,还有就是要初始化数组全部为无穷大,这样会方便求最小。当然,你也记得在初始化完后,将 \(f(0,0)\) 设置为 \(0\),这样就可以正确地转移了。

最后,你成功地通过了此题。

\(\mathtt{Code}\)

#include <iostream>
#include <iomanip>
#include <cmath>
#include <string>
#include <algorithm>
#include <cstdio>
#include <cstring>
//#define endl '\n'
#define IL inline
using namespace std;
const int N = 1e5 + 10;
const int INF = 0x3f3f3f3f;

IL int read()
{
	int x = 0,f = 1;
	char c = getchar();
	while(c <'0'|| c >'9'){if(c == '-') f = -1;c = getchar();}
	while(c >= '0' && c <= '9') x = x * 10 + c - '0',c = getchar();
	return x * f;
}

void write(int x)
{
	if(x > 9) write(x / 10);
	putchar(x % 10 + '0');
}

int n, f[105][105], c[105];
string t, s[105][105];

int main()
{
	// ios::sync_with_stdio(0);
	// cin.tie(0);
	// cout.tie(0);
	cin >> t;
	cin >> n;
	int m = t.size();
	for(int i = 1;i <= n;i++)
	{
		cin >> c[i];
		for(int j = 1;j <= c[i];j++)
		{
			cin >> s[i][j];
		}
	}
	memset(f, 0x7f, sizeof(f));
	f[0][0] = 0;
	for(int i = 1;i <= n;i++)
	{
		memcpy(f[i], f[i - 1], sizeof(f[i - 1]));
		for(int j = 1;j <= m;j++)
		{
			for(int k = 1;k <= c[i];k++)
			{
				int gft = s[i][k].size();
				if(gft > j) continue;
				if(t.substr(0, j).substr(j - gft) == s[i][k]) f[i][j] = min(f[i][j], f[i - 1][j - gft] + 1);
			}
		}
	}
	cout << (f[n][m] == 0x7f7f7f7f ? -1 : f[n][m]) << endl;
	return 0;
}
posted @ 2024-03-11 23:24  邻补角-SSA  阅读(15)  评论(0)    收藏  举报  来源