[LuoguP5319] [BJOI2019] 奥术神杖 (01分数规划+AC自动机+dp)

[LuoguP5319] [BJOI2019] 奥术神杖 (01分数规划+AC自动机+dp)

题面

神杖上从左到右镶嵌了\(n\)颗奥术宝石,奥术宝石一共有 1010 种,用数字 0123456789 表示。有些位置的宝石已经残缺,用 . 表示,你需要用完好的奥术宝石填补每一处残缺的部分(每种奥术宝石个数不限,且不能够更换未残缺的宝石)。古老的魔法书上记载了 \(m\) 种咒语 \((S_i,V_i)\),其中 \(S_i\)是一个非空数字串,\(V_i\)是这种组合能够激发的神力。

神杖的初始神力值 \(\mathrm{Magic} = 1\),每当神杖中出现了连续一段宝石与$ S_i$ 相等时,神力值 $\mathrm{Magic} $ 就会乘以$ V_i\(。但神杖如果包含了太多咒语就不再纯净导致神力降低:设\) c$ 为神杖包含的咒语个数(若咒语类别相同但出现位置不同视为多次),神杖最终的神力值为 \(\sqrt[c]{\mathrm{Magic} }\)。(若 \(c=0\) 则神杖最终神力值为\(1\)。)

你需要使修复好的神杖的最终的神力值最大,输出任何一个解即可。

分析

按照套路,把最终答案两边取\(\ln\),每个\(V_i\)选和不选记为\(x_i\)

\[\ln \sqrt[c]{Magic}=\frac{1}{c} \ln \prod V_ix_i =\frac{1}{c} \sum x_i(\ln V_i)\]

那么把\(V_i\)取\(\ln\)之后就变成了一个经典的01分数规划问题。我们可以二分答案\(mid\),考虑实际值比当前二分值更大的情况,即\(\frac{1}{c} \sum x_i(\ln V_i)>mid\), 可以转化为选取一些\(V_i\)来最大化\(\sum (\ln V_i-mid)\) ,如果这个最大值大于0,就说明实际值比当前二分值更大。

如何求\(\max(\sum (\ln V_i-mid))\)?看到多模式串匹配,我们想到在AC自动机上DP. 设\(dp_{i,j}\)表示序列的前\(i\)位在AC自动机上匹配到节点\(j\)时的最大值。那么容易写出状态转移方程:

\[dp_{i+1,\delta(j,k)}=\max(dp_{i,j}+cost(\delta(j,k))) \ (k \in \{0 ,1,2 \cdots 9 \})\]

其中\(\delta(j,k)\)表示\(j\)节点通过字符\(k\)转移后得到的新节点。\(cost(x)\)表示匹配到\(x\)节点对答案的贡献。如果原串匹配了节点\(x\)对应的串,答案将增加\(V_i-mid\).但是,匹配了\(x\)之后还可能匹配\(x\)的后缀,因此答案增加的值应该是fail树上\(x\)到初始节点的路径上所有点的\(V_i-mid\)之和。每次算一遍\(cost\)显然会超时,所以我们在BFS预处理时计算\(x\)到初始节点的路径上所有点的\(\ln V_i\)之和\(val_x\)和点的个数\(sz_x\). 这样\(cost(\delta(j,k))=val_{\delta(j,k)}-sz_{\delta(j,k)} \times mid\)

为了输出答案,还要在dp过程中记录最优转移。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#include<algorithm>
#define maxn 2000
#define maxc 10
#define INF 1e18
#define eps 1e-9
#define maxln 100
using namespace std;
int n,m;
char str[maxn+5],tmp[maxn+5];
double w[maxn+5];

struct AC_automaton {
    int ch[maxn+5][maxc];
    int sz[maxn+5];//记录x在fail树上到根的链上点的个数
    double val[maxn+5];//记录链上权值和
    int fail[maxn+5];
    int ptr;
    void insert(char *s,double v) {
        int x=0;
        int len=strlen(s+1);
        for(int i=1; i<=len; i++) {
            int c=s[i]-'0';
            if(!ch[x][c]) ch[x][c]=++ptr;
            x=ch[x][c];
        }
        sz[x]++;
        val[x]+=v;
    }
    void get_fail() {
        queue<int>q;
        for(int i=0; i<maxc; i++) if(ch[0][i]) q.push(ch[0][i]);
        while(!q.empty()) {
            int x=q.front();
            q.pop();
            sz[x]+=sz[fail[x]];
            val[x]+=val[fail[x]];
            for(int i=0; i<maxc; i++) {
                if(ch[x][i]) {
                    fail[ch[x][i]]=ch[fail[x]][i];
                    q.push(ch[x][i]);
                } else ch[x][i]=ch[fail[x]][i];
            }
        }
    }
} T;
double dp[maxn+5][maxn+5];
pair<int,int>res[maxn+5][maxn+5];//记录dp[i][j]由哪个转移过来
void print(int i,int j) {
    if(i==0) return;
    print(i-1,res[i][j].first);
    putchar(res[i][j].second+'0');
}
bool check(double mid,int type) {
    for(int i=0;i<=n;i++){
        for(int j=0;j<=T.ptr;j++){
            dp[i][j]=-INF;
            if(type) res[i][j]=make_pair(0,0);
        }
    }
    dp[0][0]=0;
    for(int i=0; i<n; i++) {
        for(int j=0; j<=T.ptr; j++) {
            if(str[i+1]=='.') { //可以选
                for(int k=0; k<maxc; k++) {
                    int nex=T.ch[j][k];
                    if(dp[i+1][nex]<dp[i][j]+T.val[nex]-mid*T.sz[nex]) { //01分数规划,每个点变成w[i]-mid
                        dp[i+1][nex]=dp[i][j]+T.val[nex]-mid*T.sz[nex];
                        if(type) res[i+1][nex]=make_pair(j,k);
                    }
                }
            } else {
                int k=str[i+1]-'0';
                int nex=T.ch[j][k];
                if(dp[i+1][nex]<dp[i][j]+T.val[nex]-mid*T.sz[nex]) { //01分数规划,每个点变成w[i]-mid
                    dp[i+1][nex]=dp[i][j]+T.val[nex]-mid*T.sz[nex];
                    if(type) res[i+1][nex]=make_pair(j,k);
                }
            }
        }
    }
    double ans=0;
    int id=0;
    for(int j=0; j<=T.ptr; j++){
        if(dp[n][j]>ans){
            id=j;
            ans=dp[n][j];
        }
    }
    if(type) print(n,id);
    return ans>=eps;
}

int main() {
    scanf("%d %d",&n,&m);
    scanf("%s",str+1);
    for(int i=1;i<=m;i++){
        scanf("%s",tmp+1);
        scanf("%lf",&w[i]);
        w[i]=log(w[i]);
        T.insert(tmp,w[i]);
    }
    T.get_fail();
    double l=0,r=maxln,mid,ans=0;
    while(fabs(r-l)>eps){
        mid=(l+r)/2;
        if(check(mid,0)){
            ans=mid;
            l=mid;
        }else r=mid;
    }
//  printf("debug: %lf\n",exp(ans));
    check(ans,1);
}

原文地址:https://www.cnblogs.com/birchtree/p/12312943.html

时间: 2024-08-27 18:25:01

[LuoguP5319] [BJOI2019] 奥术神杖 (01分数规划+AC自动机+dp)的相关文章

BZOJ.4753.[JSOI2016]最佳团体(01分数规划 树形背包DP)

题目链接 \(Description\) 每个点有费用si与价值pi,要求选一些带根的连通块,总大小为k,使得 \(\frac{∑pi}{∑si}\) 最大 \(Solution\) 01分数规划,然后dp,设f[i][j]表示i子树选j个的最大权值和,直接暴力背包转移即可 在枚举子节点选的数量时,假设x有1.2.3.4四个子节点,复杂度为 \(1*sz[1]+sz[1]*sz[2]+(sz[1]+sz[2])*sz[3]+(sz[1]+sz[2]+sz[3])*sz[4]\) 相当于每对点在L

01分数规划(转)

01分数规划 分类: DP&&记忆化搜索2013-05-04 14:47 4193人阅读 评论(1) 收藏 举报 [关键字] 0/1分数规划.最优比率生成树.最优比率环 [背景] 根据楼教主的回忆录,他曾经在某一场比赛中秒掉了一道最优比率生成树问题,导致很多人跟风失败,最终悲剧.可见最优比率生成树是多么凶残的东西,但是这个东西只要好好研究半天就可以掌握,相信你在看了我写的这篇总结之后可以像楼教主一般秒掉这类问题. 因为网上对于01分数规划问题的详细资料并不是太多,所以我就结合自己的一些理解

[BJOI2019]奥术神杖——AC自动机+DP+分数规划+二分答案

题目链接: [BJOI2019]奥术神杖 答案是$ans=\sqrt[c]{\prod_{i=1}^{c}v_{i}}=(\prod_{i=1}^{c}v_{i})^{\frac{1}{c}}$. 这样不大好求,我们将这个式子取$ln$,变成$ln\ ans=\frac{1}{c}\sum_{i=1}^{c}ln\ v_{i}$. 这显然是一个分数规划,每次二分一个答案$mid$,将每个串的权值都减去$mid$,那么只需要求最大价值是否大于$0$即可. 剩下的问题就是一个在$AC$自动机上的$D

Desert King POJ2728(Prim+迭代+0-1分数规划)

[原题地址]: POJ2728 [题目大意]: 选 \(n-1\) 条边,最小化这些边的\[\frac{\sum w_i} {\sum d_i}\] 其中 \(w_i\) 为第 \(i\) 条边的花费,\(d_i\) 为这条边所连接的两个点的距离. \(w_i\) 为连接的两个点的 \(z\) 值差的绝对值, \(d_i\) 为欧几里得距离 [题解]: 这道题是0-1分数规划的经典题目 我们令\[\frac{\sum w_i} {\sum d_i}=k\] 这样二分 \(k\) 的值 将所有的边

POJ 3621 Sightseeing Cows 【01分数规划+spfa判正环】

题目链接:http://poj.org/problem?id=3621 Sightseeing Cows Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 11526   Accepted: 3930 Description Farmer John has decided to reward his cows for their hard work by taking them on a tour of the big ci

01分数规划模板

/* 01分数规划模板(Dinkelbach) 01分数规划就是把 sum(a)/sum(b)转换成 f(x)=sum(a)-ans*sum(b); 当f(x)取到0时,ans取到了最大(小)值 poj 2976 两个长度为n的数组a,b 可以除去m个,怎样选择才能使剩下的 sum(a)/sum(b)的百分数最大 */ int n,m; struct Node{ int a,b; double v; ///用于排序筛选较大的值(题目要求极大值 bool operator<(const Node&am

poj3621 Sightseeing Cows --- 01分数规划

典型的求最优比例环问题 参考资料: http://blog.csdn.net/hhaile/article/details/8883652 此题中,给出每个点和每条边的权值,求一个环使 ans=∑点权/∑边权 最大. 因为题目要求一个环,而且必然是首尾相接的一个我们理解的纯粹的环,不可能是其他样子的环, 所以我们可以把一条边和指向的点看做整体处理. 上面方程可以化为:ans×e[i]-p[i]=0 以它为边权二分答案,spfa求负环,有负环则该ans可行,增大下界. 若一直不可行,则无解. #i

【Earthquake, 2001 Open 】 0-1 分数规划

71  奶牛施工队一场地震把约翰家园摧毁了,坚强的约翰决心重建家园.约翰已经修复了 N 个牧场,他需要再修复一些道路把它们连接起来.碰巧的是,奶牛们最近也成立了一个工程队,专门从事道路修复.而然,奶牛们很有经济头脑,如果无利可图,它们是不会干的.约翰和奶牛达成了协议,约翰向奶牛支付 F 元,奶牛负责修路,但不必修复所有道路,只要确保所有牧场连通即可.可供奶牛选择修复的道路有 M 条,第 i 条道路连接第 Ui 个牧场和第 Vi 个牧场,修复需要 Ti 分钟,支出成本为 Ci.保证连通所有牧场的道

编程之美第一篇 01分数规划

01分数规划 01分数规划问题其实就是解决单价之类的问题,假设给你n个物品,让你找出选k个物品的最大单价:例如南阳oj:Yougth的最大化:解决这类问题可以用二分查找,这类问题跟二分极大化最小值,极小化最大值有一些相似的地方,均是从结果出发,来进行二分查找:例如上面南阳那道题,可以转化一下: 由于v/w=单价:所以v=w*单价:即v-w*单价=0:有了这个关系,我们马上可以想到二分来查找这个值: 那么我们可以定义一个count数组来记录v-w*单价的值:由于选k个只需要把count从大到小排下