原文链接 https://www.cnblogs.com/cly-none/p/ECFINAL2018J.html

题意:给出一个长度为\(n\)的字符串\(s\),要求给\(s\)的每个后缀\(s[i:]\)分配权值\(k_i\)(实数),满足\(0 \leq k_i \leq 1\),且\(\sum_i k_i = 1\)。再此基础上,最大化

\[\min_{i=1}^n \left( \sum_{j=1}^n k_j {\rm {lcp}} (s[i:],s[j:]) \right)
\]

\(n \leq 2 \times 10^5\)

第一步当然是构建SAM(反串),令\(s[i:]\)在parent树上对应的结点为\(p_i\),\(dep_i\)为结点\(i\)所能表示的最长串,那么我们要最大化的就是\(\min_{i=1}^n \left( \sum_{j=1}^n k_j dep_{{\rm {lca}} (p_i,p_i)} \right)\)。

在这里树的结构恰好为权值的计算提供了一种类似分治的结构,因此我们考虑用树上dp解决这个问题。

于是我们令\(dp_i\)为仅考虑结点\(i\)的子树的答案。如果\(i\)是后缀结点,那么显然\(dp_i = dep_i\)。这样就确定了初始值。

然后就考虑合并。我们设结点\(u\)有孩子结点\(v\)。那么假设我们把\(k\)点权值分配到\(v\)的子树中,\(v\)能对答案产生的贡献就是\(dp_v k + dep_u (1-k) = (dp_v - dep_u)k + dep_u\)。我们所要做的就是对这些一次函数分配\(k\),使\(k\)的总和为\(1\),且它们的值取\(\min\)后尽可能大。注意到这些一次函数的斜率都大于零,这意味着如果分配完\(k\)后这些函数的取值不互相相等,那么可以把当前函数值最小的那个调大,当前函数值最大的那个调小,得到一个更优的答案。

因此我们要做的就是确定一条如下图所示的平行于\(x\)轴的直线,使得所有函数对应的\(x\)值之和为\(1\)。这样得到的解显然是合法的。

这当然可以二分。也能得到是根据以斜率的倒数为比例分配的。

这样就能\(O(n)\)解决本题了。

此外,这道题还有在笛卡尔树上dp的做法,与本做法没有太大区别。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
typedef pair<int,int> pii;
#define fir first
#define sec second
#define rep(i,a,b) for (int i = (a) ; i <= (b) ; ++ i)
#define rrp(i,a,b) for (int i = (a) ; i >= (b) ; -- i)
#define gc() getchar()
template <typename tp>
inline void read(tp& x) {
x = 0; char tmp; bool key = 0;
for (tmp = gc() ; !isdigit(tmp) ; tmp = gc())
key = (tmp == '-');
for ( ; isdigit(tmp) ; tmp = gc())
x = (x << 3) + (x << 1) + (tmp ^ '0');
if (key) x = -x;
} const int N = 200010;
int ch[N << 1][26], fa[N << 1], len[N << 1], cnt = 1, las = 1;
bool tag[N << 1];
void append(char c) {
int np = ++ cnt, p = las;
tag[np] = 1;
las = np;
len[np] = len[p] + 1;
fa[np] = 1;
while (p && !ch[p][c - 'a'])
ch[p][c - 'a'] = np, p = fa[p];
if (!p) return;
int q = ch[p][c - 'a'];
if (len[q] == len[p] + 1)
fa[np] = q;
else {
int nq = ++ cnt;
len[nq] = len[p] + 1;
tag[nq] = 0;
fa[nq] = fa[q];
rep (i, 0, 25) ch[nq][i] = ch[q][i];
fa[q] = nq;
fa[np] = nq;
while (p && ch[p][c - 'a'] == q)
ch[p][c - 'a'] = nq, p = fa[p];
}
}
struct edge {
int la,b;
} con[N << 1];
int tot,fir[N << 1];
void add(int from,int to) {
con[++tot] = (edge) {fir[from], to};
fir[from] = tot;
}
char str[N];
int n;
db dp[N << 1];
void dfs(int pos) {
if (tag[pos]) {
dp[pos] = len[pos];
return;
}
db tmp = 0;
for (int i = fir[pos] ; i ; i = con[i].la)
dfs(con[i].b), tmp += 1.0 / (dp[con[i].b] - len[pos]);
dp[pos] = 1.0 / tmp + len[pos];
}
void solve() {
scanf("%s", str + 1);
n = strlen(str + 1);
tot = 0;
las = cnt = 1;
memset(fir,0,sizeof(int) * (2 * (n + 5)));
memset(ch,0,sizeof(int) * (26 * 2 * (n + 5)));
rrp (i, n, 1) append(str[i]);
rep (i, 2, cnt) add(fa[i], i);
dfs(1);
printf("%.10lf\n", dp[1]);
}
int main() {
int T;
read(T);
while (T --)
solve();
return 0;
}

小结:这种问题因为能把贡献规约到每个lca处,所以可以用树形dp解决。这和笛卡尔树的分治思路是同等的。

【做题】ECFinal2018 J - Philosophical … Balance——dp的更多相关文章

  1. 2018ECfinal J. Philosophical Balance

    2018ECfinal J. Philosophical Balance 题目大意: 给出一个字符串 \(s\) ,你需要给每一个 \(i\) 一个 \([0,1]\) 之间的权值 \(k_i\) , ...

  2. 2014多校第一场J题 || HDU 4870 Rating(DP || 高斯消元)

    题目链接 题意 :小女孩注册了两个比赛的帐号,初始分值都为0,每做一次比赛如果排名在前两百名,rating涨50,否则降100,告诉你她每次比赛在前两百名的概率p,如果她每次做题都用两个账号中分数低的 ...

  3. 【做题记录】DP 杂题

    P2577 [ZJOI2004]午餐 $\texttt{solution}$ 想到贪心: 吃饭慢的先打饭节约时间, 所以先将人按吃饭时间从大到小排序. 状态: \(f[i][j]\) 表示前 \(i\ ...

  4. DP 优化方法大杂烩 & 做题记录 I.

    标 * 的是推荐阅读的部分 / 做的题目. 1. 动态 DP(DDP)算法简介 动态动态规划. 以 P4719 为例讲一讲 ddp: 1.1. 树剖解法 如果没有修改操作,那么可以设计出 DP 方案 ...

  5. 【做题】CF388D. Fox and Perfect Sets——线性基&数位dp

    原文链接https://www.cnblogs.com/cly-none/p/9711279.html 题意:求有多少个非空集合\(S \subset N\)满足,\(\forall a,b \in ...

  6. 【做题】TCSRM601 Div1 500 WinterAndSnowmen——按位考虑&dp

    原文链接https://www.cnblogs.com/cly-none/p/9695526.html 题意:求有多少对集合\(S,T\)满足:\(S \subseteq \{1,2...n \}, ...

  7. 【做题】spoj4060 A game with probability——dp

    赛前做题时忽然发现自己概率博弈类dp很弱,心好慌.(获胜概率或最优解期望) 于是就做了这道题,续了特别久. 一开始列dp式子的时候就花了很长时间,首先搞错了两次,然后忘记了根据上一轮dp值直接确定选什 ...

  8. bzoj5108 [CodePlus2017]可做题 位运算dp+离散

    [CodePlus2017]可做题 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 87  Solved: 63[Submit][Status][Dis ...

  9. 【做题】TCSRM592 Div1 500 LittleElephantAndPermutationDiv1——计数&dp

    题意:定义函数\(f(A,B) = \sum_{i=1}^n \max(A_i,B_i)\),其中\(A\)和\(B\)都是长度为\(n\)的排列.给出\(n\)和\(k\),问有多少对\((A,B) ...

随机推荐

  1. js点击复制剪贴板

    代码用原生写的.工作中用的angular,所以如果有用angular的话,请把js代码copyToClipboard函数中的document.getElementById(elementId).inn ...

  2. ES6 Symbol数据类型和set-map 数据结构

    Symbol数据类型 ES6新加的数据类型,提供一个独一无二的值 { let a1 = Symbol() ;let a2 = Symbol() } //声明 { let a3 = Symbol.for ...

  3. java项目对jar包加密流程,防止反编译

    Java 开发语言以其安全性高.代码优化.跨平台等特性,迅速取代了很多传统高级语言,占据了企业级网络应用开发等诸多领域的霸主地位.特别是近年来大数据.互联网+.云计算技术的不断发展,Java 开发语言 ...

  4. windows下复制文件报错“文件名对目标文件夹可能过长 。您可以缩短文件名并重试,或者......”

    我将一个路径下文件夹复制到另一个路径下时,出现了报错,报错图片如下: 然后查资料发现: 1.文件名长度最大为255个英文字符,其中包括文件扩展名在内.一个汉字相当于两个英文字符.2.文件的全路径名长度 ...

  5. CJSON parse.c

    #include <stdio.h> #include <stdlib.h> #include "cJSON.h" void printJson(cJSON ...

  6. 详解Java内存区域?虚拟机类加载机制?

    一.Java运行时数据区域 1.程序计数器 “线程私有”的内存,是一个较小的内存空间,它可以看做当前线程所执行的字节码的行号指示器.Java虚拟机规范中唯一一个没有OutOfMemoryError情况 ...

  7. 短信利用weixin://connectToFreeWifi/?apKey=协议跳转到微信打开落地页h5

    微信门店wifi接口,任意站跳转,跳转二维码长按识别加粉,接口支持动态传参数,支持微信支付等特殊接口对接. 代码如下使用 <head> <meta charset="utf ...

  8. 用php写一个99乘法表

    运行结果如下

  9. iOS深浅拷贝

    浅拷贝:你和你的影子,你改变,你的影子发生改变 深拷贝:你的克隆人,你改变,你的克隆人并不会发生变化 eg: NSString *string = @"我是一个小白鼠"; NSSt ...

  10. Redishelp

    /** * @author yanming.zhang * @date 2019/1/25 21:15 */ @Component public class RedisHelp { @Autowire ...