首页 > 技术文章 > 【ABC225F】String Cards

Cry-For-theMoon 2022-02-21 21:01 原文

ABC225F String Cards

题意:

给出 \(n\) 个小写字母串 \(s_1,s_2,...,s_n\),让你从中选出 \(k\) 个,然后把这个 \(k\) 个串重新排列后拼接得到一个字符串,要求是这个结果字符串的字典序最小。问这个结果串是多少。

\(k\le n\le 50,|S_i|\le 50\)

分析:

这个题,是两个比较经典的贪心模型的结合,很有意思。

1. 给定 \(n\) 个串重新排列然后拼接,使得最后的字典序最小。

原题传送门 NOIP1998提高组 选数...

好的,我们还是正经分析一下。这种给一个排列让你重排的问题基本都是考虑邻项交换。交换的条件是很好想的,就是 \(s_i+s_j\lt s_j+s_i\),但是我们得证明它是带传递性的。这里有一个很妙的想法:注意到长度相同的串比较字典序,等价于转换成 \(26\) 进制数后比较大小,越小的,字典序就越小(容易发现如果位数不同这个性质就不成立了,比如 cde)。那么我们不如设 \(s_i\) 写成数是 \(t_i\),那么等价于:

\[t_i\times 26^{|s_j|}+t_j\lt t_j\times 26^{|s_i|}+t_i \]

然后我们移项以后会发现其实比较的就是:

\[\frac{t_i}{26^{s_i}-1}\lt \frac{t_j}{26^{s_j}-1} \]

诶那这个式子的比较就肯定有传递性了。

2.给定 \(n\) 个串 \(s_1,s_2,...,s_n\),让你从中选 \(k\) 个串,按出现顺序拼接,使得字典序最小。

我们这个第一想法那就是 \(dp\):设 \(f(i,j)\) 是前 \(i\) 个串,选了 \(j\) 个出来的最小字典序结果。但是这个其实是错误的。

一个误区是,因为考虑字典序从前往后,这里的 \(dp\) 也是从前往后考虑的。但是我们来看转移:\(dp(i,j)=\min\{dp(i-1,j),dp(i-1,j-1)+s_i\}\),这个其实有点问题,你看 \(\min\) 中的第二项,它实质上是从后往前确定的:我确定末尾的结果,然后加上前面部分字典序最小的结果。

你可能还没有意识到问题,那就想想我们平时让字典序最小,是不是从第一位开始,让高位尽可能的小。换言之我们是先确定最开头的字符串,然后让后面部分字典序最小的。

所以应该倒着 \(dp\),设 \(f(i,j)\) 是后 \(i\) 个串,选 \(j\) 个出来的最小字典序结果。

我们把 1,2 两个问题结合起来,就做完了:先用 1. 问题中的排序方法把 \(s\) 排序,然后执行 2. 问题中的 \(dp\)

事实上这种手法叫做 exchange-argument,想继续了解的可以直接去 CF 搜索。大致思想是,我们按照一个顺序排序,然后进行 \(dp\)

代码:

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define op(x) ((x&1)?x+1:x-1)
#define odd(x) (x&1)
#define even(x) (!odd(x))
#define lc(x) (x<<1)
#define rc(x) (lc(x)|1)
#define lowbit(x) (x&-x)
#define Max(a,b) (a>b?a:b)
#define Min(a,b) (a<b?a:b)
#define next Cry_For_theMoon
#define il inline
#define pb(x) push_back(x)
#define is(x) insert(x)
#define sit set<int>::iterator
#define mapit map<int,int>::iterator
#define pi pair<int,int>
#define ppi pair<int,pi>
#define pp pair<pi,pi>
#define fr first
#define se second
#define vit vector<int>::iterator
#define mp(x,y) make_pair(x,y)
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef double db;
using namespace std;
const int MAXN=60;
ll n,k;
string s[MAXN];
string f[MAXN][MAXN],inf;
bool cmp(const string& s1,const string& s2){
    return s1+s2<s2+s1;
}
int main(){
    cin>>n>>k;
    rep(i,1,n)cin>>s[i];
    inf+=('z'+1);
    sort(s+1,s+1+n,cmp);
    rep(i,1,k)f[n+1][i]=inf;
    per(i,n,1)rep(j,0,k){
        f[i][j]=f[i+1][j];
        if(j)f[i][j]=min(f[i][j],s[i]+f[i+1][j-1]);
    }
    cout<<f[1][k]<<endl;
    return 0;
}

推荐阅读