P8147 [JRKSJ R4] Salieri 题解
题意:给定 个字符串 ,每个字符串有权值 。 次询问,每次给定一个字符串 和一个数 。定义 为 在 中出现次数。求 的第 大的值。
范围:,,。字符集只有 。时限 秒。
解法:
建出 AC 自动机的 树后,就等价于若干个点,支持到根路径每个点 加 。修改后求 的第 大。不存在字符串末尾的点视为 即可。
直接做不支持多组询问。不太对。考虑二分。就变成了询问有多少个点 。但是还是不太好做。
多次询问,每次给若干个点。这不是虚树吗?虚树上每条边上的点的 值都相同,等于这条边下面那个点子树和。于是这些边上要满足 。就变成了询问链上点权大于某数个数的点数。直接主席树维护即可。由于 个点建虚树只有 条边,总复杂度 。
关于虚树:
把树视为有根树。
建立虚树需要有一个关键点集合 ,通常要把根加入 ,设为 。虚树只包含所有关键点和任意一个关键点子集的 LCA。连边方式按照祖先关系。
先说怎么求虚树点集。
任意点集 的 LCA 为其中 DFS 序最小的和最大的两个点的 LCA。
记 DFS 序列最小和最大两点分别为 和 。容易知道点集 的 LCA 必然是 的 LCA 的祖先。我们只需要证明点集 中任意一个点都在 的 LCA 子树内。显然假如有一个不在子树内,那 DFS 序最小和最大的必然不是 。证毕。
于是我们只需要求出任意两点 LCA 加入虚树点集即可。然而这个复杂度还是 的,不能接受。
将点集按 DFS 序排序,取相邻两个点的 LCA 加入虚树集合即可。
本质等价于证明对于任意 , 和 的 LCA 都被加入过虚树。
归纳证明,当 时,显然。考虑 。我们知道 与 的 LCA 必然被加入过。如果 不在这两个点 LCA 子树内,那么考虑 和 的 LCA 即为 和 的 LCA。否则 和 的 LCA 在之前就加入过了。
证毕。
然后我们就做到了在 的复杂度内求出虚树点集。然后将虚树内点按 DFS 序排序,相邻两点的 LCA 连向第二个点即可。
容易发现每条边上的点对应的 都是一样的。
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <string>
#include <queue>
#include <vector>
using namespace std;
using ll = long long;
const int N = 5e5 + 5;
int n, m;
string s[N];
ll v[N];
int pos[N];
vector<int> G[N];
class ACAM
{
public:
int son[N][4];
int fail[N];
int idx;
int flag[N];
int ins(string s)
{
int u = 1;
for (auto& j : s)
{
int nj = j - 'a';
if (!son[u][nj]) son[u][nj] = ++idx;
u = son[u][nj];
}
flag[u]++;
return u;
}
void getfail()
{
queue<int> q;
fail[1] = 1;
for (int i = 0; i < 4; i++)
{
fail[son[1][i]] = 1;
if (son[1][i]) q.push(son[1][i]);
}
while (q.size())
{
int u = q.front();
q.pop();
for (int i = 0; i < 4; i++)
{
if (!son[u][i]) son[u][i] = son[fail[u]][i];
else
{
fail[son[u][i]] = son[fail[u]][i];
q.push(son[u][i]);
}
}
}
for (int i = 1; i <= idx; i++) if (!fail[i]) fail[i] = 1;
for (int i = 2; i <= idx; i++)
{
G[fail[i]].emplace_back(i);
}
}
}ac;
vector<int> total;
int rt[N];
vector<int> nv[N];
class Chairman_tree
{
public:
int idx;
struct Node
{
int lson, rson;
ll sum;
}tr[N * 21];
void pushup(int u)
{
tr[u].sum = tr[tr[u].lson].sum + tr[tr[u].rson].sum;
}
int update(int p, int l, int r, int x)
{
int u = ++idx;
tr[u] = tr[p];
if (l == r)
{
tr[u].sum++;
return u;
}
int mid = l + r >> 1;
if (x <= mid) tr[u].lson = update(tr[p].lson, l, mid, x);
else tr[u].rson = update(tr[p].rson, mid + 1, r, x);
pushup(u);
return u;
}
ll qry(int derricklo, int lt, int lt2, int nl, int nr, int l, int r)
{
if (l > r) return 0;
if (nl >= l && nr <= r) return tr[lt].sum + tr[lt2].sum - tr[derricklo].sum;
int mid = nl + nr >> 1;
ll res = 0;
if (l <= mid) res = qry(tr[derricklo].lson, tr[lt].lson, tr[lt2].lson, nl, mid, l, r);
if (r > mid) res += qry(tr[derricklo].rson, tr[lt].rson, tr[lt2].rson, mid + 1, nr, l, r);
return res;
}
}sgt;
int lt[N], dl;
int faf[N][21];
int dep[N];
void dfs(int u, int fa, int d)
{
dep[u] = d;
lt[u] = ++dl;
if (nv[u].size())
{
rt[u] = sgt.update(rt[fa], 0, N, nv[u][0]);
for (int i = 1; i < nv[u].size(); i++) rt[u] = sgt.update(rt[u], 0, N, nv[u][i]);
}
else
{
rt[u] = rt[fa];
}
for (auto& j : G[u]) dfs(j, u, d + 1);
}
class TreeCut
{
public:
int top[N], sz[N], fa[N], son[N], dep[N];
void dfs1(int u, int f)
{
fa[u] = f;
sz[u] = 1;
dep[u] = dep[f] + 1;
for (auto& j : G[u])
{
if (j != f)
{
dfs1(j, u);
sz[u] += sz[j];
if (sz[son[u]] < sz[j]) son[u] = j;
}
}
}
void dfs2(int u, int f)
{
top[u] = f;
if (!son[u]) return;
dfs2(son[u], f);
for (auto& j : G[u])
{
if (j != fa[u] && j != son[u]) dfs2(j, j);
}
}
inline int LCA(int u, int v)
{
while (top[u] ^ top[v])
{
if (dep[top[u]] < dep[top[v]]) swap(u, v);
u = fa[top[u]];
}
return (dep[u] < dep[v] ? u : v);
}
}tc;
inline bool cmp(int x, int y)
{
return lt[x] < lt[y];
}
vector<int> NG[N];
int flag[N];
int sz[N];
int cnt;
void vtdfs(int u)
{
sz[u] = flag[u];
for (auto& j : NG[u])
{
vtdfs(j);
sz[u] += sz[j];
}
}
ll nk;
void calcans(int u, int fa)
{
for (auto& j : NG[u])
{
calcans(j, u);
// val[i] * sz[j] > nk
// val[i] > nk / sz[j]
if (sz[j] && nk / sz[j] + 1 < N)
{
ll nval = nk / sz[j] + 1;
//cnt -= (1ll * nv[j] * sz[j] > nk);
cnt += sgt.qry(rt[u], 0, rt[j], 0, N, nval, N);
}
}
}
int check(ll k)
{
nk = k;
cnt = 0;
vtdfs(1);
calcans(1, 0);
return cnt;
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m;
ac.idx = 1;
for (int i = 1; i <= n; i++)
{
cin >> s[i] >> v[i];
pos[i] = ac.ins(s[i]);
nv[pos[i]].emplace_back(v[i]);
}
ac.getfail();
dfs(1, 0, 1);
tc.dfs1(1, 0);
tc.dfs2(1, 1);
while (m--)
{
string s;
int k;
cin >> s >> k;
total.clear();
int u = 1;
for (auto& j : s)
{
u = ac.son[u][j - 'a'];
total.emplace_back(u);
flag[u]++;
}
sort(total.begin(), total.end(), cmp);
// solve
vector<int> vec;
for (int i = 0; i + 1 < total.size(); i++)
{
vec.emplace_back(total[i]);
vec.emplace_back(tc.LCA(total[i], total[i + 1]));
}
vec.emplace_back(total.back());
vec.emplace_back(1);
sort(vec.begin(), vec.end(), cmp);
vec.erase(unique(vec.begin(), vec.end()), vec.end());
for (int i = 0; i + 1 < vec.size(); i++)
{
NG[tc.LCA(vec[i], vec[i + 1])].emplace_back(vec[i + 1]);
}
ll l = 0ll, r = (ll)8e8, ans = 0ll;
while (l <= r)
{
ll mid = l + r >> 1ll;
if (check(mid) < k) r = mid - 1, ans = mid;
else l = mid + 1;
}
for (auto& j : total) flag[j] = 0;
for (auto& j : vec) NG[j].clear(), sz[j] = 0;
cout << ans << "\n";
}
return 0;
}