洛谷P5319 [BJOI2019] 奥术神杖 题解
题意:
Bezorath 大陆抵抗地灾军团入侵的战争进入了僵持的阶段,世世代代生活在 Bezorath 这片大陆的精灵们开始寻找远古时代诸神遗留的神器,试图借助神器的神秘力量帮助她们战胜地灾军团。
在付出了惨痛的代价后,精灵们从步步凶险的远古战场取回了一件保存尚完好的神杖。但在经历过那场所有史书都视为禁忌的“诸神黄昏之战”后,神杖上镶嵌的奥术宝石已经残缺,神力也几乎消耗殆尽。精灵高层在至高会议中决定以举国之力收集残存至今的奥术宝石,并重金悬赏天下能工巧匠修复这件神杖。
你作为神术一脉第五百零一位传人,接受了这个艰巨而神圣的使命。
神杖上从左到右镶嵌了 n 颗奥术宝石,奥术宝石一共有 10 种,用数字
0123456789
表示。有些位置的宝石已经残缺,用.
表示,你需要用完好的奥术宝石填补每一处残缺的部分(每种奥术宝石个数不限,且不能够更换未残缺的宝石)。古老的魔法书上记载了 m 种咒语 (Si,Vi) ,其中 Si 是一个非空数字串,Vi 是这种组合能够激发的神力。神杖的初始神力值 Magic=1,每当神杖中出现了连续一段宝石与 Si 相等时,神力值 Magic 就会乘以 Vi。但神杖如果包含了太多咒语就不再纯净导致神力降低:设 c 为神杖包含的咒语个数(若咒语类别相同但出现位置不同视为多次),神杖最终的神力值为 c√Magic。(若 c=0 则神杖最终神力值为 1 )
例如有两种咒语 (01,3) 、(10,4) ,那么神杖
0101
的神力值为 3√3×4×3你需要使修复好的神杖的最终的神力值最大,输出任何一个解即可。
输入格式:
第一行两个正整数 n,m ,表示宝石数和咒语数。
第二行为一个长度为 n 的字符串 T ,表示初始的神杖。
接下来 m 行每行一个非空数字串 Si 和一个正整数 Vi ,表示每种咒语。
输出格式:
输出最终神杖上从左到右镶嵌的宝石,多解时任意输出一个即可。
数据范围:
1≤n,m≤1501, 1≤(∑mi=1|Si|)≤1501, 1≤Vi≤109 ,保证 Si 互不相同。
记神力值 Ans=c√∏ci=1wi ,对两侧取对数得
lnAns=1cc∑i=1wi>x显然这是个01分数规划。考虑二分 x ,则
1cc∑i=1wi>x⇒c∑i=1(wi−x)>0问题就变成了最大化 wi 的和。
那么对 Si 建 AC 自动机,并设 f(i,j) 为考虑 T 的前 i 位且当前在节点 j 时的最大值,则
f(i+1,c)↑f(i,j)+vc(c:=j→si+1, ti+1=si+1∨ti+1=.)时间复杂度
O(n|Σ|⋅∑|Si|log(ϵ−1log(maxVi)))其中 ϵ=10−8 表示精度,|Σ|=10 表示字符集大小。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
// #define double long double
#define INF 0x3f3f3f3f3f3f3f3f
const double eps = 1e-8;
void up(int &x, int y) { x < y ? x = y : 0; }
void down(int &x, int y) { x > y ? x = y : 0; }
int dcmp(double x) { if(fabs(x) <= eps) return 0; return x > eps ? 1 : -1; }
#define N ((int)(2019))
char t[N], ans[N]; queue<int> q;
double val[N], f[N][N];
int n, tot, ch[N][12], sum[N], fail[N], g[N][N][2];
void insert(char *s, double v)
{
int u = 0;
for(int i = 1, c; s[i]; i++)
{
c = s[i] - '0';
if(!ch[u][c]) ch[u][c] = ++tot;
u = ch[u][c];
}
++sum[u]; val[u] += v;
}
void build()
{
for(int i = 0; i < 10; i++) if(ch[0][i]) q.push(ch[0][i]);
while(!q.empty())
{
int u = q.front(); q.pop();
sum[u] += sum[fail[u]]; val[u] += val[fail[u]];
for(int i = 0; i < 10; i++)
{
if(ch[u][i])
{
fail[ch[u][i]] = ch[fail[u]][i];
q.push(ch[u][i]);
}else ch[u][i] = ch[fail[u]][i];
}
}
}
double check(double mid)
{
for(int i = 0; i <= tot; i++) val[i] -= sum[i] * mid;
for(int i = 0; i <= n; i++)
for(int j = 0; j <= tot; j++) f[i][j] = -1e111;
f[0][0] = 0;
for(int i = 0; i < n; i++)
for(int j = 0; j <= tot; j++) if(f[i][j] > -1e100)
for(int k = 0; k < 10; k++) if(t[i + 1] == '.' || t[i + 1] - '0' == k)
{
int c = ch[j][k];
if(dcmp(f[i][j] + val[c] - f[i + 1][c]) > 0)
{
f[i + 1][c] = f[i][j] + val[c];
g[i + 1][c][0] = j, g[i + 1][c][1] = k;
}
}
for(int i = 0; i <= tot; i++) val[i] += sum[i] * mid;
int pos = 0;
for(int i = 1; i <= tot; i++) if(dcmp(f[n][i] - f[n][pos]) > 0) pos = i;
for(int i = n, now = pos; i; i--) {
ans[i] = g[i][now][1] + '0', now = g[i][now][0];
}
return f[n][pos];
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
int m; cin >> n >> m >> (t + 1);
for(int i = 1, v; i <= m; i++)
{
static char s[N];
cin >> (s + 1) >> v; insert(s, log((double)v));
}
build(); double l = 0, r = log((double)1e9);
while(fabs(r - l) > eps)
{
double mid = (l + r) / 2.0;
if(dcmp(check(mid)) > 0) l = mid; else r = mid;
}
check(l); cout << (ans + 1) << '\n';
return 0;
}
参考文献:
[1] https://www.luogu.com.cn/article/5m7wm9eu
[2] https://www.luogu.com.cn/article/dunslmu5
题外话:
这 tag 太长了首页显示会有问题,完整如下:
tag: ["算法","数据结构","字符串","DP","01分数规划"]
AC 自动机怎么不是数据结构?
AC 自动机就是数据结构。AC 自动机就是数据结构。