[ICPC2018西安J] Philosophical … Balance - 后缀自动机,纳什均衡,概率期望dp

Description

给出一个字符串 \(s\) ,你需要给每一个 \(i\) 一个 \([0,1]\) 之间的权值 \(k_i\),且满足 \(\sum k_i=1\)。并且最大化 \(\min_{i=1}^n(\sum_{j=1}^nlcp(suf(s,j),suf(s,i))\times k_j)\)

Solution

在反串的 Suffix Tree 上利用 Nash 性质做概率 dp

\(f[i]\) 表示对于 \(i\) 子树内做整个游戏(此时的概率总和为 \(1\)),能获得的最大结果是多少

对于一个点 \(p\) 的子树,如果 \(p\) 本身包含一个是反串的前缀的状态,那么 \(f[p]\) 显然等于 \(len[p]\),因为一定会将所有概率分布在 \(p\)

否则,此时子树内的贡献需要进一步计算,而子树间的贡献必然是 \(len[p]\)

假设我们现在点 \(p\)\(q_1,q_2,...,q_k\) 这些孩子

那么对于 \(q_i\) 这个孩子,假设我们给他 \(x\) 的概率,那么他的贡献就是 \(len[p] + (f_{q_i}-len[p]) x\)

这个分布是先手设定的,而后手会取最小的那个,先手要使得最小值最大,根据纳什均衡,所有的取值应该相等

可以推出,最后的 \(x\) 是按照系数的倒数来分布的,因此这一去做一个树上的 dp 即可

\[f[p] = len[p] + \frac {1} {\sum_{q} \frac 1 {(f[q]-len[p])}} \]

#include <bits/stdc++.h>
using namespace std;
const int N = 2000005;
struct SAM
{
    // int len[N], ch[N][26], fa[N], ind, last;
    // int t[N], a[N], cnt[N], f[N];
    vector<int> len;
    vector<vector<int>> ch;
    vector<int> fa;
    vector<double> f;
    vector<vector<int>> g;
    vector<int> tag;
    int ind, last;
    SAM(int N)
    {
        ind = last = 1;
        len.resize(N);
        ch = vector<vector<int>>(N, vector<int>(26));
        g.resize(N);
        tag.resize(N);
        fa.resize(N);
        f.resize(N);
    }
    inline void extend(int id)
    {
        int cur = (++ind), p;
        len[cur] = len[last] + 1;
        for (p = last; p && !ch[p][id]; p = fa[p])
            ch[p][id] = cur;
        if (!p)
            fa[cur] = 1;
        else
        {
            int q = ch[p][id];
            if (len[q] == len[p] + 1)
                fa[cur] = q;
            else
            {
                int tmp = (++ind);
                len[tmp] = len[p] + 1;
                for (int i = 0; i < 26; i++)
                    ch[tmp][i] = ch[q][i];
                fa[tmp] = fa[q];
                for (; p && ch[p][id] == q; p = fa[p])
                    ch[p][id] = tmp;
                fa[cur] = fa[q] = tmp;
            }
        }
        last = cur;
    }

    void puttag(const string &str)
    {
        int p = 1;
        for (int i = 0; i < str.length(); i++)
        {
            p = ch[p][str[str.length() - 1 - i] - 'a'];
            tag[p] = 1;
        }
    }

    void dfs(int p)
    {
        if (tag[p])
        {
            f[p] = len[p];
        }
        else
        {
            double sum = 0;
            // todo: distribute by (f_q - len_p), so just sum the inv and then inv
            for (int q : g[p])
            {
                dfs(q);
                sum += 1.0 / (f[q] - len[p]);
            }
            f[p] = len[p] + 1.0 / sum;
        }
    }
    void solve()
    {
        for (int i = 1; i <= ind; i++)
            g[fa[i]].push_back(i);
        // Todo: Do dfs and calculate all f[]
        dfs(1);
        cout << fixed << setprecision(12) << f[1] << endl;
    }
};

void solve()
{
    string str;
    cin >> str;
    SAM sam(2 * str.length() + 2);
    int t, k;
    for (int i = 0; i < str.length(); i++)
        sam.extend(str[str.length() - 1 - i] - 'a');
    sam.puttag(str);
    sam.solve();
}

signed main()
{
    ios::sync_with_stdio(false);
    int t;
    cin >> t;
    while (t--)
        solve();
}

相关文章: