T1
NFLSOJ P5030 最小表示
考虑两个串本质相同的条件,发现如果计算出每一位上的字母距离它上一次出现的距离 \(dis_i\),那两个串本质相同等价于所有 \(dis_i\) 相同。注意到这个东西只和相对位置有关,所以只需要先对原串求一遍 \(dis\) 数组,然后对这个 \(dis\) 数组后缀排序一下,求出本质不同子串数即可。
但是这个事情很显然是不对的,因为比如说对于 \(\texttt{abcabcbac}\),对原串求 \(dis\) 得到 \(\{ 1, 2, 3, 2, 2, 2, 1, 3, 2 \}\),而如果考虑 \(\texttt{cbac}\) 这个后缀,\(dis\) 就变成了 \(\{ 1, 2, 3, 2 \}\)。容易发现这里对应位置上的 \(dis\) 值发生了变化。这实际上是由于后缀的起始点分隔了某些字母的相邻两次出现,从而导致后面那次出现的 \(dis\) 发生变化。注意到如果把这些发生变化的 \(dis\) 值全部变成 \(0\),也不会影响子串同构的判断。因此对于每个后缀直接把这些位置归零,然后把处理之后的后缀做后缀排序。注意,这里每个后缀是独立处理的,它们各自独立将自己的对应位置归零。
接下来考虑如何对处理之后的后缀做后缀排序。由于每个后缀是独立更改的,我们并不能很容易地完成排序。但是注意到字母只有 \(10\) 个,也就是 \(0\) 位置至多只有 \(10\) 个。因此可以对每个后缀记录该后缀的所有 \(0\) 位置,然后使用二分 + 哈希比较两个子串的字典序。如果没有删除位置,直接哈希即可。对于删除位置,由于数量很小,暴力枚举所有删除位置,将这一位对于哈希值的贡献减去即可。
完成后缀排序之后,直接根据 SA 求本质不同子串数的套路拿总共的减去每两个排名相邻的后缀的 LCP 即可。
代码
#include <iostream>
#include <algorithm>
#define int long long
using namespace std;
const int B = 233333;
const int P = 1000000007;
int a[50005], n, m;
string str;
int ap[15], aft[50005][15];
int del[50005][15];
int hsh[50005];
int pw[50005], lst[50005];
int ord[50005];
int Query(int l, int r) { return (P + hsh[r] - hsh[l - 1] * pw[r - l + 1] % P) % P; }
int chk(int l, int r) {
int ret = Query(l, r);
for (int i = 1; i <= del[l][0]; i++) {
if (del[l][i] <= r)
ret = (P + ret - pw[r - del[l][i]] * a[del[l][i]] % P) % P;
}
return ret;
}
int lcp(int a, int b) {
int l = 1, r = n - max(a, b) + 1, mid, ans = 0;
while (l <= r) {
mid = (l + r) >> 1;
if (chk(a, a + mid - 1) == chk(b, b + mid - 1))
ans = mid, l = mid + 1;
else
r = mid - 1;
}
return ans;
}
int q(int x, int y) {
int ax = a[y];
for (int i = 1; i <= del[x][0]; i++) del[x][i] == y ? (ax = 0) : 0;
return ax;
}
bool cmp(int a, int b) {
int ans = lcp(a, b);
if (ans == n - max(a, b) + 1)
return a > b;
else
return q(a, a + ans) < q(b, b + ans);
}
signed main() {
freopen("string.in", "r", stdin);
freopen("string.out", "w", stdout);
cin >> n >> m;
pw[0] = 1;
for (int i = 1; i <= n; i++) pw[i] = pw[i - 1] * B % P;
cin >> str;
str = ' ' + str;
for (int i = 1; i <= n; i++) {
a[i] = i - ap[str[i] - 'a'];
hsh[i] = (hsh[i - 1] * B + a[i]) % P;
lst[i] = ap[str[i] - 'a'];
ap[str[i] - 'a'] = i;
for (int j = lst[i] + 1; j <= i; j++) aft[j][str[i] - 'a'] = i;
}
for (int i = 1; i <= n; i++) {
for (int j = 0; j < m; j++) {
if (aft[i][j])
del[i][++del[i][0]] = aft[i][j];
}
}
for (int i = 1; i <= n; i++) ord[i] = i;
sort(ord + 1, ord + n + 1, cmp);
int ans = n * (n + 1) / 2;
for (int i = 1; i < n; i++) ans -= lcp(ord[i], ord[i + 1]);
cout << ans << "\n";
return 0;
}
T3
NFLSOJ P3361 模板性二维数点
考虑所有黑点的纵坐标都小于白点的纵坐标时会发生什么。此时我们选出的答案里必然包含黑点权值的最大值或白点权值的最大值。如果两个点可以同时选,则显然正确。否则对于任意一组解,把其中一个换成对应的权值最大值必然更优。
因此考虑对纵坐标分治,每次考虑纵坐标在分治中心之下的黑点和纵坐标在分治中心之上的白点,根据结论我们可以在这一层中选出 \(O(区间长度)\) 个有效(可能成为答案)的点对。分治有 \(O(\log n)\) 层,因此有用点对的级别在 \(O(n \log n)\)。这样就可以对每个询问离线做二维数点了。
代码
#include <iostream>
#include <algorithm>
#include <cassert>
#include <vector>
#include <map>
#define lowbit(x) ((x) & (-(x)))
using namespace std;
int n, q;
struct Point {
int x, y, w;
} val[3000005], a[100005], b[100005];
int vcnt;
struct Query {
int l, r, id;
} qs[500005];
vector<Point> va;
vector<Point> vb;
int d[1200005], dcnt;
map<int, int> mp;
void Solve(int l, int r, vector<Point> v1, vector<Point> v2) {
if (v1.empty() || l >= r)
return;
int mid = (l + r) >> 1;
vector<Point> va1, va2, vb1, vb2;
for (auto p : v1) (p.y <= mid ? va1 : va2).emplace_back(p);
for (auto p : v2) (p.y <= mid ? vb1 : vb2).emplace_back(p);
if (va1.size() && vb2.size()) {
Point p1, p2;
p1.w = p2.w = 0;
for (auto p : va1) p.w > p1.w ? (p1 = p) : p;
for (auto p : vb2) {
p.w > p2.w ? (p2 = p) : p;
val[++vcnt] = (Point) { p1.x, p.x, p1.w + p.w };
}
for (auto p : va1) p.x != p1.x ? (val[++vcnt] = (Point) { p.x, p2.x, p.w + p2.w }) : p1;
}
Solve(l, mid, va1, vb1);
Solve(mid + 1, r, va2, vb2);
}
struct BIT {
int bit[1000005];
void add(int x, int y) { for (; x <= 1000000; x += lowbit(x)) bit[x] = max(bit[x], y); }
int query(int x) {
int ret = 0;
for (; x; x -= lowbit(x)) ret = max(ret, bit[x]);
return ret;
}
} bit;
struct SBIT {
int bit[1000005];
void add(int x, int y) { for (; x; x -= lowbit(x)) bit[x] = max(bit[x], y); }
int query(int x) {
int ret = 0;
for (; x <= 1000000; x += lowbit(x)) ret = max(ret, bit[x]);
return ret;
}
} bit2;
int ans[500005];
int main() {
freopen("template.in", "r", stdin);
freopen("template.out", "w", stdout);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i].x >> a[i].y >> a[i].w;
}
for (int i = 1; i <= n; i++) {
cin >> b[i].x >> b[i].y >> b[i].w;
}
cin >> q;
for (int i = 1; i <= q; i++) cin >> qs[i].l >> qs[i].r, qs[i].id = i;
for (int i = 1; i <= n; i++) d[++dcnt] = a[i].x, d[++dcnt] = b[i].x;
for (int i = 1; i <= q; i++) d[++dcnt] = qs[i].l, d[++dcnt] = qs[i].r;
d[++dcnt] = 0;
sort(d + 1, d + dcnt + 1);
d[0] = 2147483647;
mp[d[0]] = -n - q - 1;
for (int i = 1; i <= dcnt; i++) d[i] != d[i - 1] ? (mp[d[i]] = mp[d[i - 1]] + 1) : 0;
for (int i = 1; i <= n; i++) a[i].x = mp[a[i].x], b[i].x = mp[b[i].x];
for (int i = 1; i <= q; i++) qs[i].l = mp[qs[i].l], qs[i].r = mp[qs[i].r];
dcnt = 0;
for (int i = 1; i <= n; i++) d[++dcnt] = a[i].y, d[++dcnt] = b[i].y;
sort(d + 1, d + dcnt + 1);
mp.clear();
for (int i = 1; i <= dcnt; i++) d[i] != d[i - 1] ? (mp[d[i]] = mp[d[i - 1]] + 1) : 0;
for (int i = 1; i <= n; i++) a[i].y = mp[a[i].y], b[i].y = mp[b[i].y];
for (int i = 1; i <= n; i++) {
va.emplace_back(a[i]);
vb.emplace_back(b[i]);
}
Solve(1, n * 2, va, vb);
sort(val + 1, val + vcnt + 1, [](Point a, Point b) { return a.x < b.x; });
sort(qs + 1, qs + q + 1, [](Query a, Query b) { return a.l < b.l; });
for (int i = 1, j = 1; i <= q; i++) {
while (j <= vcnt && val[j].x < qs[i].l) bit2.add(val[j].y, val[j].w), ++j;
ans[qs[i].id] = bit2.query(qs[i].r);
}
for (int i = q, j = vcnt; i; i--) {
while (j > 0 && val[j].x > qs[i].l) bit.add(val[j].y, val[j].w), --j;
ans[qs[i].id] = max(ans[qs[i].id], bit.query(qs[i].r));
}
for (int i = 1; i <= q; i++) cout << (ans[i] == 0 ? -1 : ans[i]) << "\n";
return 0;
}
考虑可能对答案有贡献的元素。
标签:qs,50005,20240705,int,mid,后缀,dis From: https://www.cnblogs.com/forgotmyhandle/p/18288501