1. 程式人生 > 實用技巧 >[模擬賽]戒指 題解

[模擬賽]戒指 題解

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. 1 <= T <= 15
  2. 1 <= N <= 50,1 <= M <= 100
  3. 1 <= 單詞長度 <= 10
  4. 1 <= Hi <= 100
  5. 資料保證不出現重複的單詞

輸出

對於每組資料輸出一行,為在戒指上刻入的字串。
如果有多解,則輸出長度最短的解,若依然有多接則輸出字典序最小的解。
答案有可能為空串。

樣例

輸入:

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;
}