Ring
题意
有 n 个 “快乐”字符串 si , 每个字符串有一个快乐值 ai 。
一个字符串的快乐值总和 = 每个“快乐”字符串出现的次数 * 该字符串的快乐值 。
要求找到长度不大于m的 , 快乐值最大的字符串 。
如果多个相同,则输出长度最短且字典序最小的。数据范围如上。
样例
思路
AC自动机fail指针:
if(nxt[now][i]==-1)
nxt[now][i] = nxt[fail[now]][i];
else
fail[nxt[now][i]] = nxt[fail[now]][i];
构建fail指针后,每个结尾字符串将指向其第一次出现位置的后继节点(大概是这样吧…)
例如样例1可以看成这样:
理解了fail指针后本题的dp转移方程就很好推了:
dp[i][j] = max( dp[i][j] , dp[i-1][nxt[j]] + end[j] )
其中 i 为字符串长度 , j 为节点编号 , nxt[j] 为 j 节点的前驱节点 ,end[j] 即ac自动机的结尾标记,本题记录的是字符串的快乐值 。
比较烦的是记录路径以及输出字典序和长度最小,这里可以通过一个 string path[i][j] 来记录每个 dp[i][j] 对应的路径。详见代码。
代码
#include<bits/stdc++.h>
using namespace std;
struct trie{
int nxt[5050][26];
int fail[5050];
int end[5050];
int dp[55][5050];
string path[55][5050];
int root,idx;
int newnode(){
for(int i=0;i<26;i++){
nxt[idx][i] = -1;
}
end[idx++] = 0;
return idx-1;
}
void init(){
idx = 0;
root = newnode();
memset(dp,-1,sizeof dp);
}
void insert(char buf[],int val){
int len = strlen(buf);
int now = root;
for(int i=0;i<len;i++){
if(nxt[now][buf[i]-'a']==-1)
nxt[now][buf[i]-'a'] = newnode();
now = nxt[now][buf[i]-'a'];
}
end[now] = val; // 结尾标记上快乐值
}
void build(){
queue<int> q;
fail[root] = root;
for(int i=0;i<26;i++){
if(nxt[root][i]==-1)
nxt[root][i] = root;
else{
fail[nxt[root][i]] = root;
q.push(nxt[root][i]);
}
}
while(!q.empty()){
int now = q.front();
q.pop();
for(int i=0;i<26;i++){
if(nxt[now][i]==-1){
nxt[now][i] = nxt[fail[now]][i];
}
else{
fail[nxt[now][i]] = nxt[fail[now]][i];
q.push(nxt[now][i]);
}
}
}
}
string query(int m){
int ans = 0;
string lll;
for(int i=0;i<=m;i++){
for(int j=0;j<idx;j++){
path[i][j].clear();
}
}
dp[0][0] = 0;
for(int i=0;i<m;i++){
for(int j=0;j<idx;j++){
for(int k=0;k<26;k++){
//排除访问不到的情况,例如trie树中很深的节点不可逆在一开始就被访问,
//所以dp数组除dp[0][0]外初始化为-1
if(dp[i][j]==-1) continue;
int to = nxt[j][k]; // 后继节点
if(dp[i+1][to]<dp[i][j]+end[to]){
dp[i+1][to] = dp[i][j]+end[to];
path[i+1][to] = path[i][j];
path[i+1][to].push_back(char(k+'a'));
}
// dp值相等时通过长度和字典序判断取或不取
else if(dp[i+1][to]==dp[i][j]+end[to]){
string tmp = path[i][j];
tmp.push_back(char(k+'a'));
if(path[i+1][to].empty()||tmp.length()<path[i+1][to].length())
path[i+1][to] = tmp;
else if(path[i+1][to].length()<tmp.length())
continue;
else
path[i+1][to] = min(path[i+1][to],tmp);
}
if(dp[i+1][to]>ans){
ans = dp[i+1][to];
lll = path[i+1][to];
}
else if(dp[i+1][to]==ans){
if(path[i+1][to].length()<lll.length())
lll = path[i+1][to];
else if(lll.length()<path[i+1][to].length())
continue;
else
lll = min(lll,path[i+1][to]);
}
}
}
}
//cout<<ans<<" "<<lll<<endl;
return lll;
}
}ac;
int n,m;
char s[110][110];
int a[110];
int main(){
ios::sync_with_stdio(false);
int t;
cin>>t;
while(t--){
cin>>m>>n;
for(int i=0;i<n;i++)
cin>>s[i];
for(int i=0;i<n;i++)
cin>>a[i];
ac.init();
for(int i=0;i<n;i++)
ac.insert(s[i],a[i]);
ac.build();
cout<<ac.query(m)<<endl;
}
return 0;
}