题解: AT_abc225_f_String Cards
题解: AT_abc225_f_String Cards
AT_abc225_f_String Cards
贪心+DP
分析
首先考虑一个类似的问题:Luogu_P1012 [NOIP 1998 提高组] 拼数
解决这个问题的关键在于一种特殊的排序函数:
bool cmp(string A,string B)
{
return A+B<B+A;
}
这个结论成立的前提是:如果 \(A<B\) 那么 \(A+C<B+C\),显然对于数字成立。
排完序后选择的前后位置也就被确定了下来,这道题多出了一个选 \(k\) 个的条件,是不是意味着我们排完序后直接从前往后 DP 就行了?
状态:设 \(f_{i,j}\) 表示将排序后前 \(1\sim i\) 的串选 \(j\) 个拼在一起的字典序最小的串。
边界:\(f_{0,0}=0\),其余为无穷大。
目标:\(f_{n,k}\)
转移:\(f_{i,j}=min(f_{i-1,j},f_{i-1,j-1}+st[i])\),注意到 \(st_i\) 要拼在后边,满足排序后的相对顺序。
别急,看组数据:
3 2
bba
bb
bba
我们被 hack 了,思考原因,注意到数字的大小比较是右对齐进行比较的,因此从左数的长度不影响,因此上一题的前提成立;而字典序是左对齐进行比较的,因此我们应该从右往左 DP。
即字典序存在另一个性质:如果 \(A<B\),那么 \(C+A<C+B\)。
状态:设 \(f_{i,j}\) 表示将排序后前 \(i\sim n\) 的串选 \(j\) 个拼在一起的字典序最小的串。
边界:\(f_{n+1,0}=0\),其余为无穷大。
目标:\(f_{1,k}\)
转移:\(f_{i,j}=min(f_{i+1,j},st[i]+f_{i+1,j-1})\),注意到 \(st_i\) 要拼在左边,满足排序后的相对顺序。
代码
//AT_abc225_f
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 55;
int n, k;
string st[N];
struct node
{
vector <int> vec;
inline string trans()
{
if (vec.empty())
return "{}";
string res;
for (int i = 0; i < vec.size(); i ++)
res += st[vec[i]];
return res;
}
inline void ins(int pos, int val)
{
vec.insert(vec.begin() + pos - 1, val);
}
inline void del(int pos)
{
vec.erase(vec.begin() + pos - 1);
}
inline friend bool operator < (node a, node b)
{
return a.trans() < b.trans();
}
} f[N][N];
bool cmp(string a, string b)
{
return a + b < b + a;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i ++)
cin >> st[i];
sort(st + 1, st + n + 1, cmp);
for (int i = n; i >= 1; i --)
for (int j = 1; j <= min(k, n - i + 1); j ++)
{
f[i][j] = f[i + 1][j];
f[i + 1][j - 1].ins(1, i);
f[i][j] = min(f[i][j], f[i + 1][j - 1]);
f[i + 1][j - 1].del(1);
}
cout << f[1][k].trans() << endl;
return 0;
}

浙公网安备 33010602011771号