2024.1.2做题纪要
P4022 [CTSC2012] 熟悉的文章
单调队列“板子题”。
可以看出,要先对标准作文库建个广义 \(SAM\),对于答案的可能性,还是很明显是单调的。
该考虑怎么 \(check\),贪心??好像不太对,不让双 \(log\),所以只能 \(O(N)\) 的 \(check\)。不能贪心,和数学也没啥关系,只剩下 \(dp\) 了。
考虑 \(dp\),我们设 \(dp[i]\) 表示枚举到第 \(i\) 位时,最长合法的匹配的位置有多少个,则存在转移式:
\[dp[i] = max\{dp[i - 1], max_{j = i - Len[i]}^{i - x} \{dp[j] + i - j \} \}
\]
其中每个变量的含义见这个(其实我是褐的上面的链接的做法写的QAQ)
桃乃木かな
#include <bits/stdc++.h>
const int MAXN = 2e6 + 1000;
class Trie {
private:
int root, tot;
public:
int last[MAXN], number[MAXN];
int child[MAXN][2];
Trie() {
root = tot = 1;
last[root] = 1;
}
void Build(std::string &str) {
int now = root;
for (int i = 0; i < (int)str.size(); ++ i) {
int ch = str[i] - '0';
if (!child[now][ch]) {
child[now][ch] = ++ tot;
number[tot] = ch;
}
now = child[now][ch];
}
}
}trie;
class Suffix_Automaton {
private:
int root, tot;
public:
int child[MAXN][2];
int link[MAXN], length[MAXN];
Suffix_Automaton() {
root = tot = 1;
}
int Insert(int c, int last) {
int p = last, newP = ++ tot;
length[newP] = length[p] + 1;
while (p && !child[p][c]) {
child[p][c] = newP;
p = link[p];
}
if (!p) {
link[newP] = root;
}
else {
int q = child[p][c];
if (length[q] == length[p] + 1) {
link[newP] = q;
}
else {
int newQ = ++ tot;
memcpy(child[newQ], child[q], sizeof child[q]);
length[newQ] = length[p] + 1;
link[newQ] = link[q];
link[q] = link[newP] = newQ;
while (p && child[p][c] == q) {
child[p][c] = newQ;
p = link[p];
}
}
}
return newP;
}
}suffixAutomaton;
void Bfs() {
std::queue<std::pair<int, int>> queue;
for (int i = 0; i <= 1; ++ i)
if (trie.child[1][i])
queue.emplace(1, trie.child[1][i]);
while (queue.size()) {
int father = queue.front().first;
int now = queue.front().second;
queue.pop();
trie.last[now] = suffixAutomaton.Insert(trie.number[now], trie.last[father]);
for (int i = 0; i <= 1; ++ i)
if (trie.child[now][i])
queue.emplace(now, trie.child[now][i]);
}
}
int N, M, n;
std::string text;
int to[MAXN];
void Update() {
int now = 1, len = 0;
for (int i = 0; i < n; ++ i) {
int ch = text[i] - '0';
while (now && !suffixAutomaton.child[now][ch]) {
now = suffixAutomaton.link[now];
len = std::min(i + 1, suffixAutomaton.length[now]);
}
if (now) {
len ++;
to[i + 1] = i + 1 - len + 1;
now = suffixAutomaton.child[now][ch];
}
else {
now = 1;
}
}
}
class Monotonic_Queue {
private:
int l, r;
std::pair<int, int> number[MAXN / 2];
public:
void Clear() {
l = 1;
r = 0;
}
void CheckRange(int L) {
while (l <= r && number[l].first < L)
l ++;
}
int Get() {
if (l <= r)
return number[l].second;
else
return -1e9;
}
void Insert(int pos, int value) {
std::pair<int, int> add = std::make_pair(pos, value);
while (l <= r && number[r].second <= value)
r --;
number[++ r] = add;
}
}queue;
int dp[MAXN / 2];
int check(int x) {
for (int i = 0; i <= n; ++ i)
dp[i] = 0;
queue.Clear();
for (int i = x; i <= n; ++ i) {
queue.Insert(i - x, dp[i - x] - (i - x));
queue.CheckRange(to[i] - 1);
dp[i] = std::max(dp[i - 1], queue.Get() + i);
}
return dp[n];
}
int Answer() {
int l = 1, r = n;
int mid, result = 0;
while (l <= r) {
mid = (l + r) >> 1;
if (10 * check(mid) >= 9 * n) {
l = mid + 1;
result = mid;
}
else {
r = mid - 1;
}
}
return result;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> N >> M;
for (int i = 1; i <= M; ++ i) {
std::cin >> text;
trie.Build(text);
}
Bfs();
for (int i = 1; i <= N; ++ i) {
std::cin >> text;
n = text.size();
Update();
std::cout << Answer() << '\n';
}
return 0;
}
P4770 [NOI2018] 你的名字
绝世好题,但是毒瘤。
先考虑当 \(l = 1, r = |S|\) 时,所以设 \(dp\) 式子就行了,这个 \(dp\) 式子很好啊。
剩下的可以用线段树合并维护 \(S\) 串的 \(parent\) 树的每个节点的 \(right(endpos)\) 集合。
更新在 \(T\) 串的每个位置的 \(length\) 的时候查询区间有没有数就行了。
具体见 https://www.luogu.com.cn/blog/stars/solution-p4770。写的很好。
みかみ ゆあ
#include <bits/stdc++.h>
const int MAXN = 3e6 + 100;
const int TREE = 40 * (1e6 + 100);
int range;
class Segment_Tree {
#define lid (lson[id])
#define rid (rson[id])
private:
void Pushup(int id) {
summary[id] = summary[lid] + summary[rid];
}
public:
int root[TREE], tot;
int lson[TREE], rson[TREE];
int summary[TREE];
Segment_Tree() {
tot = 0;
}
int Update(int id, int l, int r, int position) {
if (!id)
id = ++ tot;
if (l == r) {
summary[id] ++;
return id;
}
int mid = (l + r) >> 1;
if (position <= mid)
lid = Update(lid, l, mid, position);
else
rid = Update(rid, mid + 1, r, position);
Pushup(id);
return id;
}
int Merge(int a, int b, int l, int r) {
if (!a || !b)
return (a | b);
int New = ++ tot;
summary[New] = summary[a];
if (l == r) {
summary[New] += summary[b];
return New;
}
int mid = (l + r) >> 1;
lson[New] = Merge(lson[a], lson[b], l, mid);
rson[New] = Merge(rson[a], rson[b], mid + 1, r);
Pushup(New);
return New;
}
bool Query(int id, int l, int r, int askL, int askR) {
if (id == 0 || !summary[id])
return false;
if (askL <= l && r <= askR)
return summary[id];
int mid = (l + r) >> 1;
bool result = false;
if (askL <= mid)
result |= Query(lid, l, mid, askL, askR);
if (mid + 1 <= askR)
result |= Query(rid, mid + 1, r, askL, askR);
return result;
}
}tree;
class Suffix_Automaton {
public:
int root, last, tot;
int child[MAXN][26];
int link[MAXN], minPos[MAXN];;
long long length[MAXN];
Suffix_Automaton() {
for (int i = 1; i <= 3e6 + 20; ++ i)
minPos[i] = 1e9;
root = tot = last = 1;
}
void Insert(int c, int pos, bool flag) {
int p = last, newP = ++ tot;
last = newP;
length[newP] = length[p] + 1;
if (flag == 0)
tree.root[newP] = tree.Update(tree.root[newP], 1, range, pos);
else
minPos[newP] = pos;
while (p && !child[p][c]) {
child[p][c] = newP;
p = link[p];
}
if (!p) {
link[newP] = root;
}
else {
int q = child[p][c];
if (length[q] == length[p] + 1) {
link[newP] = q;
}
else {
int newQ = ++ tot;
memcpy(child[newQ], child[q], sizeof child[q]);
length[newQ] = length[p] + 1;
link[newQ] = link[q];
link[q] = link[newP] = newQ;
while (p && child[p][c] == q) {
child[p][c] = newQ;
p = link[p];
}
}
}
}
void Clear() {
for (int i = 1; i <= tot; ++ i) {
minPos[i] = 1e9;
memset(child[i], 0, sizeof(child[i]));
}
root = tot = last = 1;
}
}text, compare;
long long length[MAXN], answer;
void GetLength(const std::string &T, int rangeL, int rangeR) {
int now = 1, len = 0;
for (int i = 0; i < (int)T.size(); ++ i) {
int ch = T[i] - 'a';
while (true) {
int next = text.child[now][ch];
if (next != 0 && tree.Query(tree.root[next], 1, range, rangeL + len, rangeR)) {
now = next;
++ len;
break;
}
if (len == 0)
break;
-- len;
if (len == text.length[text.link[now]])
now = text.link[now];
}
length[i + 1] = len;
}
}
int degree[MAXN];
void Topo(Suffix_Automaton &SAM, bool flag) {
for (int i = 1; i <= SAM.tot; ++ i)
degree[SAM.link[i]] ++;
std::queue<int> queue;
for (int i = 1; i <= SAM.tot; ++ i)
if (!degree[i])
queue.emplace(i);
while (queue.size()) {
int now = queue.front();
int to = SAM.link[now];
queue.pop();
degree[to] --;
if (flag == false)
tree.root[to] = tree.Merge(tree.root[to], tree.root[now], 1, range);
else
SAM.minPos[to] = std::min(SAM.minPos[to], SAM.minPos[now]);
if (!degree[to] && to)
queue.emplace(to);
}
}
void GetAnswer() {
answer = 0;
for (int i = 2; i <= compare.tot; ++ i) {
int father = compare.link[i];
int minPosition = compare.minPos[i];
answer += std::max(compare.length[i] - std::max(length[minPosition], compare.length[father]) , 0ll);
}
}
int N;
std::string S, T;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
std::cin >> S >> N;
range = S.size();
for (int i = 0; i < (int)S.size(); ++ i)
text.Insert(S[i] - 'a', i + 1, 0);
Topo(text, 0);
for (int i = 1, l, r; i <= N; ++ i) {
std::cin >> T >> l >> r;
compare.Clear();
for (int j = 0; j < (int)T.size(); ++ j)
compare.Insert(T[j] - 'a', j + 1, 1);
GetLength(T, l, r);
Topo(compare, true);
GetAnswer();
std::cout << answer << '\n';
}
}
/*
acb
1
caa
*/