zoukankan      html  css  js  c++  java
  • LOJ 3089: 洛谷 P5319: 「BJOI2019」奥术神杖

    题目传送门:LOJ #3089

    题意简述:

    有一个长度为 (n) 的母串,其中某些位置已固定,另一些位置可以任意填。

    同时给定 (m) 个小串,第 (i) 个为 (S_i),所有位置都已固定,它的价值为 (V_i)

    每次每个小串在母串中出现一次,便会给答案的多重集贡献一个 (V_i)

    最终的答案为多重集的几何平均数,定义空集的几何平均数为 (1)

    请你求出一个合法母串(往可以填的位置填合法字符)使得答案最大。

    (1le n,sle 1501)(1le V_ile max V=10^9),其中 (displaystyle s=sum_{i=1}^{m}|S_i|)

    题解:

    假设多重集的大小为 (c),第 (i) 个元素为 (w_i),则 (displaystylemathrm{Ans}=sqrt[c]{prod_{i=1}^{c}w_i})

    两边取对数,有 (displaystylelnmathrm{Ans}=frac{1}{c}sum_{i=1}^{c}ln w_i),转化为经典的 0/1 分数规划问题。

    二分答案,若等式右边大于 (mathrm{mid}),则有:

    (egin{aligned}frac{1}{c}sum_{i=1}^{c}ln w_i&>mathrm{mid}\sum_{i=1}^{c}ln w_i&>ccdotmathrm{mid}\sum_{i=1}^{c}(ln w_i-mathrm{mid})&>0end{aligned})

    所以,建出小串的 AC 自动机,然后二分答案后在 AC 自动机上 DP 判断不等式是否满足。

    DP 时每个小串的权值设为 (ln V_i-mathrm{mid}),注意要记录最佳转移点,以输出方案。

    下面是代码,复杂度 (mathcal{O}(sSigma(logmax V-logepsilon)))

    #include <cstdio>
    #include <cmath>
    
    typedef double f64;
    const int MN = 1505, Sig = 10;
    const f64 eps = 1e-6, inf = 1e99;
    
    int N, M;
    char T[MN];
    
    char str[MN];
    int ch[MN][Sig], fail[MN], sum[MN], cnt;
    f64 val[MN];
    
    inline void Insert(char *s, f64 v) {
    	int now = 0;
    	for (; *s; ++s) {
    		if (!ch[now][*s & 15]) ch[now][*s & 15] = ++cnt;
    		now = ch[now][*s & 15];
    	} ++sum[now], val[now] += v;
    }
    
    int que[MN], l, r;
    void BuildAC() {
    	fail[0] = -1;
    	que[l = r = 1] = 0;
    	while (l <= r) {
    		int u = que[l++];
    		for (int i = 0; i < Sig; ++i) {
    			if (ch[u][i]) {
    				int x = fail[u];
    				while (~x && !ch[x][i]) x = fail[x];
    				if (~x) fail[ch[u][i]] = ch[x][i];
    				que[++r] = ch[u][i];
    			}
    			else if (~fail[u]) ch[u][i] = ch[fail[u]][i];
    		}
    	}
    	for (int i = 2; i <= r; ++i)
    		sum[que[i]] += sum[fail[que[i]]],
    		val[que[i]] += val[fail[que[i]]];
    }
    
    f64 f[MN][MN];
    int g[MN][MN][2];
    char AT[MN];
    inline f64 DP(f64 V) {
    	for (int j = 0; j <= cnt; ++j) val[j] -= sum[j] * V;
    	for (int i = 0; i <= N; ++i)
    		for (int j = 0; j <= cnt; ++j)
    			f[i][j] = -inf;
    	f[0][0] = 0;
    	for (int i = 0; i < N; ++i) {
    		for (int j = 0; j <= cnt; ++j) {
    			if (f[i][j] == -inf) continue;
    			if (T[i] == '.') {
    				for (int k = 0; k < Sig; ++k) {
    					int _j = ch[j][k];
    					if (f[i + 1][_j] < f[i][j] + val[_j])
    						f[i + 1][_j] = f[i][j] + val[_j],
    						g[i + 1][_j][0] = j,
    						g[i + 1][_j][1] = k;
    				}
    			}
    			else {
    				int _j = ch[j][T[i] & 15];
    				if (f[i + 1][_j] < f[i][j] + val[_j])
    					f[i + 1][_j] = f[i][j] + val[_j],
    					g[i + 1][_j][0] = j,
    					g[i + 1][_j][1] = T[i] & 15;
    			}
    		}
    	}
    	for (int j = 0; j <= cnt; ++j) val[j] += sum[j] * V;
    	int ans = 0;
    	for (int j = 1; j <= cnt; ++j)
    		if (f[N][j] > f[N][ans]) ans = j;
    	for (int i = N, j = ans; i >= 1; --i)
    		AT[i - 1] = g[i][j][1] | 48,
    		j = g[i][j][0];
    	return f[N][ans];
    }
    
    int main() {
    	scanf("%d%d", &N, &M);
    	scanf("%s", T);
    	for (int i = 1; i <= M; ++i) {
    		f64 v;
    		scanf("%s%lf", str, &v);
    		Insert(str, log(v));
    	}
    	BuildAC();
    	f64 l = 0, r = log(1e9 + 5), mid, ans = 0;
    	while (r - l > eps) {
    		mid = (l + r) / 2;
    		if (DP(mid) > 0) ans = mid, l = mid;
    		else r = mid;
    	}
    	DP(ans);
    	printf("%s
    ", AT);
    	return 0;
    }
    
  • 相关阅读:
    poj 2411 Mondriaan's Dream 骨牌铺放 状压dp
    zoj 3471 Most Powerful (有向图)最大生成树 状压dp
    poj 2280 Islands and Bridges 哈密尔顿路 状压dp
    hdu 3001 Travelling 经过所有点(最多两次)的最短路径 三进制状压dp
    poj 3311 Hie with the Pie 经过所有点(可重)的最短路径 floyd + 状压dp
    poj 1185 炮兵阵地 状压dp
    poj 3254 Corn Fields 状压dp入门
    loj 6278 6279 数列分块入门 2 3
    VIM记事——大小写转换
    DKIM支持样本上传做检测的网站
  • 原文地址:https://www.cnblogs.com/PinkRabbit/p/BJOI2019D1T1.html
Copyright © 2011-2022 走看看