[SCOI2012]喵星球上的点名
题目描述
a180285 幸运地被选做了地球到喵星球的留学生。他发现喵星人在上课前的点名现象非常有趣。
假设课堂上有 \(n\) 个喵星人,每个喵星人的名字由姓和名构成。喵星球上的老师会选择 \(m\) 个串来点名,每次读出一个串的时候,如果这个串是一个喵星人的姓或名的子串,那么这个喵星人就必须答到。
然而,由于喵星人的字码如此古怪,以至于不能用 ASCII 码来表示。为了方便描述,a180285 决定用数串来表示喵星人的名字。
现在你能帮助 a180285 统计每次点名的时候有多少喵星人答到,以及 \(m\) 次点名结束后每个喵星人答到多少次吗?
输入格式
首先定义喵星球上的字符串给定方法:
先给出一个正整数 \(l\),表示字符串的长度,接下来 \(l\) 个整数,第 \(i\) 个整数 \(a_i\) 表示字符串的第 \(i\) 个字符。
输入的第一行有两个整数,分别表示喵星人的个数 \(n\) 个点名次数 \(m\)。
接下来 \(n\) 行,每行两个喵星球上的字符串,按照定义的方法给出,依次表示第 \(i\) 只喵的姓和名。
接下来 \(m\) 行,每行一个喵星球上的字符串,表示一个老师点名的串。
输出格式
对于每个老师点名的串,输出一行一个整数表示有多少只喵答到。
然后在最后一行输出 \(n\) 个用空格隔开的整数,第 \(i\) 个整数表示第 \(i\) 个喵星人被点到的次数。
数据规模与约定
- 对于 \(30\%\) 的数据,保证 \(n, m \le 10^3\),喵星人的名字总长不超过 \(4\times10^3\),点名串的总长不超过 \(2\times10^3\)。
- 对于\(100\%\) 的数据,保证 \(1 \leq n\le 5 \times 10^4\),\(1 \leq m \le 10^5\),喵星人的名字总长和点名串的总长分别不超过 \(10^5\),保证喵星人的字符串中作为字符存在的数不超过 \(10^4\) 。
思路
- 将所有名字和点名串连起来,跑一遍 SA。
- 对于第一问就是询问区间内有多少种颜色。(HH的项链做法,莫队或者树状数组)
- 第二问比较有意思,询问每种颜色被多少种区间覆盖。可以在访问到新的颜色的时候,给他的答案加上剩余询问个数(莫队排序后),去掉新的颜色时,给他的答案减去剩余询问个数。
- 也可以统计每个点作为区间第一个出现的点的贡献累加到该颜色的答案上。
- 时间复杂度:\(O((|s|+|t|) * (\sqrt(|s|+|t|) + log(|s|+|t|)))\)
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define re _read
#define ALL(x) (x).begin(),(x).end()
#define SZ(v) ((int)v.size())
#define fi first
#define se second
#define pb push_back
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define endl "\n"
using namespace std;
mt19937 mrand(random_device{}());
int rnd(int x) { return mrand() % x;}
template <typename T> std::ostream &operator<<(std::ostream &out, const std::vector<T> &v) { out << "["; bool first = true; for (auto &&e : v) { if (first) { first = false;} else {out << ", ";} out << e; } return out << "]"; }
template <typename A, typename B> std::ostream &operator<<(std::ostream &out, const std::pair<A, B> &v) { return out << "(" << v.first << ", " << v.second << ")"; }
template <typename K> std::ostream &operator<<(std::ostream &out, const std::set<K> &s) { out << "{"; bool first = true; for (auto &&k : s) { if (first) { first = false; } else { out << ", "; } out << k; } return out << "}"; }
template <typename K, typename V> std::ostream &operator<<(std::ostream &out, const std::map<K, V> &m) { out << "{"; bool first = true; for (auto &&[k, v] : m) { if (first) { first = false; } else { out << ", "; } out << k << ": " << v; } return out << "}"; }
template <class T> vector<vector<T>> Vector(int n, int m) { return vector<vector<T>> (n, vector<T> (m, 0)); }
template <class T> vector<vector<vector<T>>> Vector(int i, int j, int k) { return vector<vector<vector<T>>> (i, vector<vector<T>>(j, vector<T>(k, 0))); }
template <typename T> void OUT(T* a, int l, int r) { for (int i = l; i <= r; i ++) cout << a[i] << " "; puts(""); }
template<class T>
inline void _read(T& x) {
static T ans;
static unsigned int c;
static bool p;
for (c = getchar(); c != '-' && (c < '0' || c > '9'); c = getchar());
if (c == '-') p = false, c = getchar(); else p = true;
for (ans = 0; c <= '9' && c >= '0'; c = getchar()) ans = ans * 10 + c - '0';
x = p ? ans : -ans;
}
/*----------------------------------------------------------------------------------------------------*/
const int INF = 10001, N = 4e5 + 10;
#define RMQ
const int maxn = 4e5 + 10;
struct SA {
#ifndef RMQ
struct Segment_Tree {
#define ls u << 1
#define rs u << 1 | 1
int min_val[maxn << 2];
void pushup(int u) {
min_val[u] = min(min_val[ls], min_val[rs]);
}
void build(int u, int l, int r, int* h) {
if (l == r) {
min_val[u] = h[l];
return ;
}
int mid = (l + r) >> 1;
build(ls, l, mid, h), build(rs, mid + 1, r, h);
pushup(u);
}
int query(int u, int l, int r, int ql, int qr) {
if (l > qr || ql > r) return 0x3f3f3f3f;
if (ql <= l && r <= qr) return min_val[u];
int mid = (l + r) >> 1;
return min(query(ls, l, mid, ql, qr), query(rs, mid + 1, r, ql, qr));
}
}segtree;
#else
int st[maxn][20], lg[maxn];
void init_st() {
for (int i = 2; i < maxn; i++) lg[i] = lg[i / 2] + 1;
for (int i = 1; i <= n; ++i) st[i][0] = height[i];
for (int j = 1; (1 << j) <= n; ++j) {
for (int i = 1; i <= (n - (1 << j) + 1); ++i) {
st[i][j] = min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
}
}
#endif
/*height[i] = lcp(S[sa[i]],S[sa[i-1]]), h[i]=height[rk[i]], h[i]>=h[i-1]-1, lcp(s[i],s[j])=min(height[rk[i]+1],...,height[rk[j]])*/
int n, sa[maxn], rk[maxn], id[maxn], cnt[maxn], height[maxn], px[maxn];
void clear() {
for (int i = 1; i <= n; i++) sa[i] = rk[i] = id[i] = height[i] = px[i] = 0;
}
void get_sa(vector<int>& s, int _n) { // get sa and height
n = _n;
int m = INF + 5, p = 0; // m 是值域, 初始化为字符集大小
for (int i = 0; i <= m; i++) cnt[i] = 0;
for (int i = 1; i <= n; ++i) cnt[rk[i] = (int)s[i]] ++; // 先对1个字符大小的子串进行计数排序
for (int i = 1; i <= m; ++i) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; --i) sa[cnt[rk[i]]--] = i;
for (int w = 1; w <= n; w <<= 1, m = p, p = 0) { // m=p 就是优化计数排序值域
for (int i = n - w + 1; i <= n; ++i) // 第二关键字无穷小先放进去
id[++p] = i;
for (int i = 1; i <= n; ++i)
if (sa[i] > w) id[++p] = sa[i] - w; // 顺次放入 s[sa[i]-w] 的第二关键字排名
for (int i = 0; i <= m; ++i) cnt[i] = 0;
for (int i = 1; i <= n; ++i) ++cnt[rk[i]], px[i] = rk[id[i]];
for (int i = 1; i <= m; ++i) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; --i) sa[cnt[px[i]]--] = id[i];
for (int i = 1; i <= n; ++i) swap(rk[i], id[i]);
rk[sa[1]] = p = 1;
for (int i = 2; i <= n; ++i) {
rk[sa[i]] = (id[sa[i]] == id[sa[i - 1]] && id[sa[i] + w] == id[sa[i - 1] + w] ? p : ++p);
}
if (p >= n) { // 排名已经更新出来了
break;
}
}
}
void get_height(vector<int>& s){
for (int i = 1, k = 0; i <= n; ++i) { // 获取 height数组
if (k) --k;
int j = sa[rk[i] - 1];
while (s[i + k] == s[j + k]) ++k;
height[rk[i]] = k;
}
#ifdef _DEBUG
for (int i = 1; i <= n; ++i)
cout<<"height["<<i<<"] = "<<height[i]<<endl;
#endif
}
void init() {
#ifndef RMQ
segtree.build(1, 1, n, height);
#else
init_st();
#endif
}
int get_lcp(int x, int y) {
int rkx = rk[x], rky = rk[y];
if (rkx > rky) swap(rkx, rky);
rkx++;
#ifndef RMQ
int lcp = segtree.query(1, 1, n, rkx, rky);
#else
int k = lg[(rky - rkx + 1)];
int lcp = min(st[rkx][k], st[rky - (1 << k) + 1][k]);
#endif
#ifdef _DEBUG
cout<<"[getlcp] x="<<x<<" y="<<y<<" rkx="<<rkx<<" rky="<<rky<<" lcp="<<lcp<<endl;
#endif
return lcp;
}
}sa;
/*-------------------------------------------------------------------------------------------------------------------*/
int color[N], len, res, cnt[N], ans1[N];
ll ans2[N];
#define get(x) (x / len)
struct Q {
int l, r, id;
}q[N];
bool cmp(const Q& a, const Q& b){ // 按照奇偶性排序,玄学优化可能快一倍
return get(a.l) ^ get(b.l) ? get(a.l) < get(b.l) : ((get(a.l) & 1) ? a.r < b.r : a.r > b.r);
}
void add(int c, int qnum) {
if (!c) return;
if (!cnt[c]) res++, ans2[c] += qnum;
cnt[c]++;
}
void del(int c, int qnum) {
if (!c) return;
cnt[c]--;
if (!cnt[c]) res--, ans2[c] -= qnum;
}
int main() {
int n, m;
re(n), re(m);
vector<int> name;
vector<PII> pos(n), sz1(n);
int now = 1;
name.pb(INF + 3);
for (int i = 0; i < n; i++) {
int tot;
re(tot);
sz1[i].first = tot;
pos[i].first = now;
for (int j = 0; j < tot; j++) {
int x;
re(x);
name.pb(x);
now++;
}
name.pb(INF), now++;
pos[i].second = now;
re(tot);
sz1[i].second = tot;
for (int j = 0; j < tot; j++) {
int x;
re(x);
name.pb(x);
now++;
}
name.pb(INF), now++;
}
vector<int> pos2(m), sz2(m);
for (int i = 0; i < m; i++) {
int tot;
re(tot);
sz2[i] = tot;
pos2[i] = now;
for (int j = 0, x; j < tot; j++) {
re(x);
now++;
name.pb(x);
}
name.pb(INF + 1), now++;
}
int L = now - 1;
sa.get_sa(name, L);
sa.get_height(name);
sa.init();
len = max(1, (int)sqrtl(1ll * L * L * m));
for (int i = 0; i < m; i++) {
auto& [ql, qr, id] = q[i];
id = i;
int l = 1, r = sa.rk[pos2[i]];
while (l < r) {
int mid = (l + r) >> 1;
if (sa.get_lcp(sa.sa[mid], pos2[i]) >= sz2[i]) r = mid;
else l = mid + 1;
}
ql = l;
l = sa.rk[pos2[i]], r = L;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (sa.get_lcp(sa.sa[mid], pos2[i]) >= sz2[i]) l = mid;
else r = mid - 1;
}
qr = l;
}
sort(q, q + m, cmp);
for (int i = 0; i < n; i++) {
for (int j = 0; j < sz1[i].first; j++)
color[sa.rk[pos[i].first + j]] = i + 1;
for (int j = 0; j < sz1[i].second; j++)
color[sa.rk[pos[i].second + j]] = i + 1;
}
for (int i = 0, l = 1, r = 0; i < m; i++) {
auto [ql, qr, id] = q[i];
while (l > ql) --l, add(color[l], m - i);
while (r < qr) ++r, add(color[r], m - i);
while (l < ql) del(color[l++], m - i);
while (r > qr) del(color[r--], m - i);
ans1[id] = res;
}
for (int i = 0; i < m; i++) printf("%d\n", ans1[i]);
for (int i = 1; i <= n; i++) printf("%lld ", ans2[i]);
return 0;
}
标签:星人,int,mid,++,P2336,SA,now,莫队,sa
From: https://www.cnblogs.com/Roshin/p/P2336.html