题目:
http://acm.ecnu.edu.cn/problem/3261/
题意:
有一句句子因为粘贴的时候出现了一点问题空格全部丢失了。现在给一本字典,每个词都对应这个词出现的频率(每十亿)。根据这个频率,我们可以根据下面的公式算出这个词带来的收益 P(word):
P(word)=len2(word)⋅ln(frequency(word))
其中 frequency 就是上面所提到的频率。len 指的是单词的长度。
特别的,对于字典中没有出现过的词,P(word)=0。
请对句子进行适当的分割,使得分割得到的所有词收益之和最大。同一个词可以重复出现,收益算作多次。
Input
先给出一本词典,词典的第一行是词条数(词条数约为 40 000),下面每行分别是单词和对应出现频率,用空格隔开。单词中只会出现英文字母大小写,不会有多余的空格。每个单词只会出现一次。频率是一个正实数。
所有单词长度之和不超过 3⋅105,最长单词长度不超过 30。
接下来一行一个整数 T (T≤10),表示有 T 个查询。
下面 T 行,每行一个句子,句子长度不超过 5 000。句子中保证只含有英文字母大小写。注意单词匹配时,不区分大小写。
词典数据来源于 Wikipedia Project Gutenberg(可能需要代理),其中 1-10000 词汇。
查询数据来源于 IELTS Test。
Output
对于每组数据,输出两行。
第一行是一个实数,表示最大能达到的收益。输出和答案相差不超过 10−3 即可认为正确。
第二行输出一连串单词,单词和单词之间用空格隔开。满足:
把这些单词依次串联起来可以得到原句子;
所有单词的收益值相加得到第一行的实数。
思路:
首先把每个单词与其价值关联起来便于查询,用字典树或者STL的map皆可,担心map超时,用了字典树。接下来便是如何去划分单词,定义
dp[i]
为以句子中第i个字母结尾时能得到的最大价值,可以得到状态转移方程:
dp[i]=max(dp[i],dp[j]+val)
,其中
0<=j<i
,表示取以第
j
个字母为结尾得到的价值加上从
#include <bits/stdc++.h>
using namespace std;
const int N = 5000 + 10;
struct trie
{
double val;
trie *next[26];
trie()
{
val = 0.0;
memset(next, 0, sizeof next);
}
}*root;
char str[N], tmp[N];
double dp[N];
int pre[N];
bool vis[N];
void trie_insert(char *s, double val)
{
trie *p = root;
for(int i = 0; s[i]; i++)
{
int j = s[i] - 'a';
if(p->next[j] == NULL) p->next[j] = new trie;
p = p->next[j];
}
p->val = val;
}
double trie_query(int l, int r, char *s)
{
if(r - l + 1 > 30) return 0.0;
trie *p = root;
for(int i = l; i <= r; i++)
{
int j = s[i] - 'a';
if(p->next[j] == NULL) return 0.0;
p = p->next[j];
}
return p->val;
}
void trie_del(trie *p)
{
for(int i = 0; i < 26; i++)
if(p->next[i] != NULL) trie_del(p->next[i]);
delete p;
}
void work(char *str)
{
int len = strlen(str + 1);
for(int i = 1; i <= len; i++)
tmp[i] = str[i], str[i] = tolower(str[i]);
for(int i = 0; i <= len; i++)
dp[i] = -1e8, pre[i] = -1, vis[i] = false;
dp[0] = 0.0;
for(int i = 1; i <= len; i++)
{
for(int j = max(i - 30, 0); j < i; j++)
{
double val = trie_query(j+1, i, str);
if(dp[i] < dp[j] + val)
{
dp[i] = dp[j] + val;
pre[i] = j;
}
}
}
printf("%.6f\n", dp[len]);
int id = len;
while(id != -1) vis[pre[id]] = true, id = pre[id];//从末尾开始寻找前驱并记录
for(int i = 1; i <= len; i++)
{
printf("%c", tmp[i]);
if(vis[i]) printf(" ");
}
printf("\n");
}
int main()
{
int n, m;
while(~ scanf("%d", &n))
{
root = new trie;
char s[50];
double val;
for(int i = 1; i <= n; i++)
{
scanf("%s%lf", s, &val);
int len = strlen(s);
for(int j = 0; j < len; j++) s[j] = tolower(s[j]);
trie_insert(s, len * len * log(val));
}
scanf("%d", &m);
while(m--)
{
scanf("%s", str+1);
work(str);
}
trie_del(root);
}
return 0;
}