[模擬賽]戒指 題解
Before the Beginning
轉載請將本段放在文章開頭顯眼處,如有二次創作請標明。
原文連結:https://www.codein.icu/gm1055/
前言
噁心的動態規劃,各種奇怪的細節,對著資料調了一個下午才調出來。
題面
描述
S計劃送給J一枚戒指,以示他們的愛情天長地久。
同時,S打算在戒指上刻寫一些語句,以增加它的寓意。可惜戒指的大小有限,最多隻能被刻入N個字母。細心的S非常瞭解J,知道她喜歡的單詞,例如love, forever等等,同時他也知道這些單詞都存在一個“愉悅值”,值越高的單詞越能取悅J。
現在,S希望刻入一條字串,使得各個單詞在字串中出現的次數 × 單詞愉悅值之和儘可能的大。
輸入
首行為一個整數T,表示資料個數。
每組資料第一行為兩個整數N、M,分別表示能刻入戒指的字串長度以及J喜歡的單詞個數。
之後為M行,每行為一個單詞Si,表示J喜歡的第i個單詞。單詞僅包含小寫字母。
之後一行包含M個正整數Hi,表示第i個單詞的取悅值。
- 1 <= T <= 15
- 1 <= N <= 50,1 <= M <= 100
- 1 <= 單詞長度 <= 10
- 1 <= Hi <= 100
- 資料保證不出現重複的單詞
輸出
對於每組資料輸出一行,為在戒指上刻入的字串。
如果有多解,則輸出長度最短的解,若依然有多接則輸出字典序最小的解。
答案有可能為空串。
樣例
輸入:
2
7 2
love
ever
5 5
5 1
ab
5
輸出:
lovever
abab
解法
一開始想爆搜,後來發現可以動態規劃。
定義 \(f(i,j)\) 為用了前 \(i\) 個字元,最後一個單詞為 \(j\) 的最大貢獻。
轉移時,對於每個 \(f(i,j)\),列舉上一個單詞 \(k\),即可從 \(f(i - len(j),k)\) 轉移。
聽起來似乎很美好,但需要考慮上一個單詞的字尾是這個單詞字首的情況。
例如樣例中:
love
ever
因此利用 kmp 的思想,預處理出 \(next(i,j)\),代表 \(i\) 的真字尾與 \(j\) 的真字首的最大相同長度。
對於完全相同的串需要特判處理,否則將會得到 \(next(i,i) = len(i)\)
題目雖然保證了給出的串不相同,但我們仍可能需要重複加串,因此 \(next(i,i)\) 是必要的。例如:
abab
除此之外,還要求最短、字典序最小。
最短很好處理,在列舉 \(i\) 的過程中更新答案,首先獲取的一定是最短的,但字典序非常麻煩。
一開始考慮排序,利用列舉順序來保證字典序,但由於種種原因,這是錯誤的。
因此我們可以對每個 \(f(i,j)\) 的狀態,儲存取得該狀態的原串 \(as(i,j)\),在轉移時如果值相同,則比較 \(as(i,j)\) 來保證字典序即可。
此外還有空串的情況,轉移過程中也要避免從非法狀態轉移過來,因此需要處理各種邊界條件。
為了方便比較字典序使用了 string,用上了多年不用的 iostream,扔掉了數十行的快讀板子……
程式碼又臭又長,各種亂搞,僅供參考吧……
再良心地給出資料:
input
output
//這字典序也太噁心了!!!
//直接存全串亂比亂搞
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast","-funroll-loops")
#pragma GCC target("ssse3","sse3","sse2","sse","avx2","avx")
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
const int maxn = 110,maxm = 110;
int T,n,m;
int f[maxn][maxm],next[maxm][maxm];
string as[maxn][maxm];
struct node
{
string ss;
int val;
bool operator<(const node &b) const
{
return ss < b.ss;
}
}s[maxm];
int ans,ansi,ansj;
namespace kmp
{
char s[200];
int len;
int pi[maxn];
inline int solve(int eq)
{
for(int i = 2,k = 0;i<=len;++i)
{
while(k && s[i] != s[k + 1]) k = pi[k];
if(s[i] == s[k + 1]) ++k;
pi[i] = k;
}
if(eq && pi[len] == (len - 1) / 2) pi[len] = pi[pi[len]];
return pi[len];
}
}
int main()
{
cin.tie(0), ios::sync_with_stdio(false), cout.tie(0);
cin >> T;
while(T--)
{
cin >> n >> m;
for(int i = 1;i<=m;++i) for(int j = 1;j<=m;++j) next[i][j] = 0;
for(int i = 1;i<=n;++i) for(int j = 1;j<=m;++j) f[i][j] = 0,as[i][j].clear();
for (int i = 1; i <= m; ++i) cin >> s[i].ss;
for (int i = 1; i <= m; ++i) cin >> s[i].val;
std::sort(s + 1,s + 1 + m);
ans = 0,ansi = ansj = 0;
for(int i = 1;i<=m;++i)
for(int j = 1;j<=m;++j)
{
kmp::len = 0;
for (string::iterator it = s[j].ss.begin(); it != s[j].ss.end(); ++it) kmp::s[++kmp::len] = *it;
kmp::s[++kmp::len] = '\0';
for (string::iterator it = s[i].ss.begin(); it != s[i].ss.end(); ++it) kmp::s[++kmp::len] = *it;
next[i][j] = kmp::solve(i == j);
}
for (int i = 1; i <= m; ++i)
{
int len = s[i].ss.length();
if(len > n) continue;
f[len][i] = s[i].val, as[len][i] = s[i].ss;
if (f[len][i] > ans || (f[len][i] == ans && as[len][i] < as[ansi][ansj])) ans = f[len][i], ansi = len, ansj = i;
}
for (int i = 1; i <= n; ++i)
for(int k = 1;k<=m;++k)
for(int j = 1;j<=m;++j)
{
int len = s[j].ss.length();
if ((int)(i - len + next[k][j] - s[k].ss.length()) < 0) continue;
int nval = f[i - len + next[k][j]][k] + s[j].val;
if (f[i][j] < nval) f[i][j] = nval, as[i][j] = as[i - len + next[k][j]][k] + s[j].ss.substr(next[k][j]);
else if(f[i][j] == nval)
{
string ns = as[i - len + next[k][j]][k] + s[j].ss.substr(next[k][j]);
if(ns < as[i][j]) as[i][j] = ns;
}
if (f[i][j] > ans || (f[i][j] == ans && as[i][j] < as[ansi][ansj])) ans = f[i][j], ansi = i, ansj = j;
}
cout << as[ansi][ansj] << endl;
}
return 0;
}