文章目录
- title
- solution
- code
title
solution
令Magic=Vi×Vj×Vk...Magic=V_i\times V_j\times V_k...Magic=Vi×Vj×Vk...
这里对Magicc\sqrt[c]{Magic}cMagic有一个很巧妙的转换——取对数
Magicc=(Magic)1c=eloge(Magic)1c\sqrt[c]{Magic}=(Magic)^{\frac{1}{c}}=e^{log_e(Magic)^{\frac{1}{c}}}cMagic=(Magic)c1=eloge(Magic)c1logeMagic1c=1clogeMagic=1cloge(Vi×Vj×Vk...)log_e\ Magic^{\frac{1}{c}}=\frac{1}{c}log_e\ Magic=\frac{1}{c}log_e(V_i\times V_j\times V_k...)loge Magicc1=c1loge Magic=c1loge(Vi×Vj×Vk...)=1c×(logeVi+logeVj+logeVk...)=\frac{1}{c}\times (log_eV_i+log_eV_j+log_eV_k...)=c1×(logeVi+logeVj+logeVk...)
于是就成功把神力值的相乘开方变为了相加
(logeVi+logeVj+logeVk...c)max(\frac{log_eV_i+log_eV_j+log_eV_k...}{c})_{max}(clogeVi+logeVj+logeVk...)max👉这个狮子到这一步就长得很像0/1分数规划问题了
实际上ta就似
考虑直接二分最后的答案
然后对咒语建ACACAC自动机,在自动机上跑0/10/10/1分数规划DPDPDP,判断答案的大小
建failfailfail指针时,顺路把从根节点到nownownow节点这一条串包含的咒语的信息进行类似前缀和的处理
设dp[i][j]:dp[i][j]:dp[i][j]:表示仅考虑前iii个宝石,现在指向自动机上的jjj节点时的最大神力值
由iii个宝石要转移到i+1i+1i+1,就要看iii宝石究竟长什么模样
①:残缺宝石,这个时候需要对始终可能都进行转移
②:完整宝石,是什么就转移什么
to:to:to: iii转移i+1i+1i+1后jjj指向自动机上的节点
dp[i+1][to]=max(dp[i][j]+val[to])dp[i+1][to]=max(dp[i][j]+val[to])dp[i+1][to]=max(dp[i][j]+val[to])
code
#include <cmath>
#include <queue>
#include <cstdio>
#include <cstring>
using namespace std;
#define inf 1e18
#define maxn 1600
#define eps 1e-7
int n, m, tot;
queue < int > q;
char s[maxn], T[maxn], path[maxn];
int sum[maxn], fail[maxn];
double val[maxn];
int trie[maxn][15];
double dp[maxn][maxn];
int g[maxn][maxn][2];void insert( double v ) {int now = 0, len = strlen( s );for( int i = 0;i < len;i ++ ) {int to = s[i] - '0';if( ! trie[now][to] ) trie[now][to] = ++ tot;now = trie[now][to];}sum[now] ++, val[now] += v;
}void Fail() {while( ! q.empty() ) q.pop();fail[0] = 0;for( int i = 0;i < 10;i ++ )if( trie[0][i] ) {fail[trie[0][i]] = 0;q.push( trie[0][i] );}while( ! q.empty() ) {int now = q.front(); q.pop();sum[now] += sum[fail[now]];val[now] += val[fail[now]];for( int i = 0;i < 10;i ++ ) {if( trie[now][i] ) {fail[trie[now][i]] = trie[fail[now]][i];q.push( trie[now][i] );}elsetrie[now][i] = trie[fail[now]][i];}}
}double DP( double x ) {for( int i = 0;i <= n;i ++ )for( int j = 0;j <= tot;j ++ )dp[i][j] = -inf;for( int i = 0;i <= tot;i ++ ) val[i] -= sum[i] * x;dp[0][0] = 0;for( int i = 0;i < n;i ++ ) { //前i个字符for( int j = 0;j <= tot;j ++ ) { //现在在AC自动机上的j节点if( dp[i][j] <= -inf ) continue; //dp[i][j]:最大魔术值for( int k = 0;k < 10;k ++ ) if( T[i] == '.' || T[i] == k + '0' ) {int to = trie[j][k];if( dp[i + 1][to] < dp[i][j] + val[to] ) {dp[i + 1][to] = dp[i][j] + val[to];g[i + 1][to][0] = j, g[i + 1][to][1] = k;//0:记录点 1:记录边的字符 后面还原路径用}}}}for( int i = 0;i <= tot;i ++ ) val[i] += sum[i] * x;int ans = 0;for( int i = 0;i <= tot;i ++ )if( dp[n][i] > dp[n][ans] ) ans = i;int pos = ans;for( int i = n;i;i -- )path[i] = g[i][pos][1] + '0', pos = g[i][pos][0];return dp[n][ans];
}int main() {scanf( "%d %d %s", &n, &m, T );for( int i = 1, v;i <= m;i ++ ) {scanf( "%s %d", s, &v );insert( log( v ) );}Fail();double l = 0, r = 1e9, ans;while( r - l > eps ) {double mid = ( l + r ) / 2;if( DP( mid ) > 0 ) ans = mid, l = mid;else r = mid;}DP( ans );printf( "%s", path + 1 );return 0;
}