链接:https://ac.nowcoder.com/acm/problem/244822
来源:牛客网
题目描述
Alice have n strings named S1 to Sn . Each string has a length li . Now she wants to type them on the txt-editor (Sequence not required).For each string Si , she has two choice.
The first choice is to type all characters of Si one by one (this will cost li ).
The second choice is to choose one string Sj , which Alice has typed before , then paste it into a single line (this will cost k) . Now we call this string S , Alice can perform the following operation any time to get Si .
- Delete any character from S , each character costs 1 .
- Insert any character anywhere in S , each character costs 1 .
Such as we want to get abac from bdc , we will do the following operations :
1. Paste bdc into a new line , cost k .
2. Delete the 2nd character d in bdc to get bc , cost 1 .
3. Insert a before the first character of bc to get abc , cost 1 .
4. Insert a after the second character of abc to get abac , cost 1 .
Totally cost k+3.
Now Alice wants to type all the strings , please help her to find the minimum cost .
输入描述:
The first line contains two integer n(1≤n≤10e2),k(1≤k≤10e2) .The next nn lines , each line contains an integer li(li(1≤li≤10e2), and a string Si with length li , Separates by a space . Si only has lowercase letters .
输出描述:
Print an integer --- the minimum cost .
示例1
输入
2 1
4 baba
4 abaa
输出
7
说明
In this sample , Alice can type the second string abaa first , cost 4 , then copy this string , cost 1 , cost 2 to get baba from abaa (abaa->aba->baba) . The total cost is 7 .
题意:要打印出n个字符串。有两种操作:
第一种操作就是把该字符串i一个一个打进去,花费li(字符串的长度).
第二种操作就是复制已经存在的字符串,然后粘贴到单独一行,花费k。对粘贴的字符串可以对其每个字符进行删除和插入操作,让它变成目标字符串,每次删除和插入操作花费1.
求打印出n个字符串的最低花费为多少。
看了官方题解,这题用LCS+最小生成树去写。。。第一次知道最小生成树可以这样用....长见识了Ort...
把每个字符串都看成一个点。 然后创建一个虚点,连接每个字符串点,边权为该字符串的长度(这相当于操作一, 逐一打印字符串的每个字符)。再然后让每个字符串点两两相连,边权为 该两字符串的长度分别减去该两字符串的最长公共子序列的长度 再相加,然后再加上复制粘贴的费用k,
即li-LCS(i, j)+lj-LCS(i, j)+k。(因为一个字符串通过增删成另一个字符串的最小花费就是只保留它俩的最长公共子序列,其他都删掉,然后加上所缺的那部分字符)(这相当于操作二)
这样图构成后,直接求它的最小生成树即可。 这样它整体的花费就是最小的。
代码如下:
#include<iostream>
#include<cstring>
#include<string>
#include<queue>
using namespace std;
const int N = 105, INF = 0x3f3f3f3f;
struct node{
int u, v, e; //u和v是边的两个端点,e是存边权
bool operator <(const node &a) const //从小到大排序
{
return e>a.e;
}
};
int rt[N]; //并查集数组,用来求最小生成树用
string s[N];
priority_queue<node> q; //用堆把存进来的边从小到大排序
int LCS(string a, string b) //求最长公共子序列
{
int f[N][N] {0};
for(int i=0; i<a.size(); i++)
for(int j=0; j<b.size(); j++)
{
if(a[i]!=b[j])
{
if(i==0 && j==0) f[i][j] = 0;
else if(i==0) f[i][j] = f[i][j-1];
else if(j==0) f[i][j] = f[i-1][j];
else f[i][j] = max(f[i-1][j], f[i][j-1]);
}
else
{
if(i==0 || j==0) f[i][j] = 1;
else f[i][j] = max(f[i][j], f[i-1][j-1]) + 1;
}
}
return f[a.size()-1][b.size()-1];
}
int Find(int x) //求根节点
{
return x==rt[x]? x : (rt[x]=Find(rt[x]));
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int n, k;
cin >> n >> k;
for(int i=1; i<=n; i++) rt[i] = i;
for(int i=1; i<=n; i++)
{
int x;
cin >> x >> s[i];
q.push({0, i, x}); //求虚点和其他点之间的边权
}
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
{
if(i==j) continue;
int tmp = LCS(s[i], s[j]);
int val = s[i].size() - tmp + s[j].size() - tmp + k; //求每两个点之间的边权
q.push({i, j, val});
}
int res = 0;
while(!q.empty())
{
node k = q.top();
q.pop();
int rx = Find(k.u), ry = Find(k.v);
if(rx==ry) continue; //两者根相等,表示两点已经在树里面了
else
{
rt[rx] = ry; //不在里面,则合并
res += k.e;
}
}
cout << res << endl;
return 0;
}