首页 > 其他分享 >【SA+启发式合并+主席树】P7361拜神

【SA+启发式合并+主席树】P7361拜神

时间:2022-11-03 17:14:52浏览次数:78  
标签:tmp 拜神 tr id int P7361 SA sa define

题目链接

由于自己感觉写不明白,先来写题解

学习自 Alex_wei

题意

给定长为 \(n\leq 5\times 10^4\) 字符串,\(q\leq 10^5\) 次询问,每次询问 \([l,r]\) 子串内出现两次以上的最长子串长度。

思路

  • 长度为 \(L\) 子串出现两次以上,等价于存在 \(l \leq i \leq j \leq r - L + 1\) 且 \(lcp(i,j)\geq L\) ,满足该条件当且仅当若将所有
    \(\geq L\) 的 \(ht_x\) 值对应的两个位置 \(sa_{x - 1}\) 与 \(sa_x\) 之间连边,\(i, j\) 在同一连通块,可以用并查集维护。
  • 而答案 \(L\) 显然满足二分性,题目可以在 \(O(nlogn)\) 的 check 复杂度通过。
  • 借鉴品酒大会的经验,我们从 height 值从大到小加点,同时并查集维护合并。每次 check 找 \([l, r - L + 1]\) 内有没有两个点满足条件。
  • 可以记录每个后缀在 height 中的后继节点,用数据结构来维护这些节点对应后继节点的下标值,这样每次只用查询 \([l,r-L+1]\) 的点中是否存在最小值小于等于 \(r-L+1\) 。
  • 而由于我们没有离线询问,在线操作,L 不规律变化,故需要对长度 \(L\) 持久化,也就是用持久化线段树维护。
  • 对于并查集合并后每个点后继节点的维护,可以用启发式合并来优雅暴力,对每个代表元开一个 set,新加入节点二分大于等于他的点 \(suf\) 和小于他的点 \(pre\)。插入后更新 \(suf\) 和 插入点即可。
  • 在单次合并后可能有多个点的 \(suf\) 值发生了改变,加入到容器中,按下标排序,从小到大依次进持久化线段树中更新。
  • 时空复杂度: \(O(nlog^2n)\)

实现细节

  • 持久化线段树不是像普通主席树的使用,考虑一下每个点的后继在更新中只会不断变小。修改原根的值不影响答案。持久化的目的是方便找出对于每个长度他所对应的所有节点下标位置。
    而不是传统的前缀信息相减的持久化。
  • 所以每次修改一系列数的时候,对于旧根没有的节点直接新建节点,已有的叶子直接修改叶子的值。
  • 查询的时候,直接找长度对应的根,只会走向大于等于 L 以前的路径,找到对应的节点。
  • 二分的时候,查询区间应该在 \([L,R-m]\) ,端点只能在这些范围。二分长度范围是 \([0, R-L]\) ,都是一些细节性的东西。

Code

#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;
}
/*----------------------------------------------------------------------------------------------------*/

#define RMQ
const int maxn = 1e6 + 10;
struct SA {
    int n, sa[maxn], rk[maxn], id[maxn], cnt[maxn], height[maxn], px[maxn];
    void get_sa(const char* s, int _n) {
        n = _n;
        int m = 300, p = 0;
        for (int i = 0; i <= m; i++) cnt[i] = 0;
        for (int i = 1; i <= n; i++) cnt[rk[i] = (int)s[i]] ++;
        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) {
            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;
            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(const char* s){
        for (int i = 1, k = 0; i <= n; i++) {
            if (k) -- k;
            int j = sa[rk[i] - 1];
            while (s[i + k] == s[j + k]) ++ k;
            height[rk[i]] = k;
        }
    }
} sa;

const int N = 5e4 + 10, INF = 1e9;
vector<PII> tmp;

// #define ROSHIN

// log =n * log

struct Chairman_Tree {
    int node_cnt, root[N];
    struct T {
        int l, r;
        int v;
    }tr[N << 8];
    int get_node() {
        node_cnt++;
        tr[node_cnt].l = tr[node_cnt].r = 0, tr[node_cnt].v = INF;
        return node_cnt;
    }
    void pushup(T& rt) {
        rt.v = min(tr[rt.l].v, tr[rt.r].v);
    }
    void build(int& u, int l, int r) {
        u = get_node();
        if (l == r) return ;
        int mid = (l + r) >> 1;
        build(tr[u].l, l, mid), build(tr[u].r, mid + 1, r);

    }
    void modify(int pre, int& u, int l, int r) {
        if (tmp.empty() || tmp.back().first > r) return ;
        assert(tmp.back().first >= l);
        u = get_node();     // 新建节点。
        tr[u].l = tr[pre].l, tr[u].r = tr[pre].r;
        if (l == r) {
            #ifdef ROSHIN
            printf("l=%d v=%d\n", l, tmp.back().second);
            #endif
            tr[u].v = tmp.back().second;
            tmp.pop_back();
            return ;
        }
        int mid = (l + r) >> 1;
        modify(tr[pre].l, tr[u].l, l, mid);
        modify(tr[pre].r, tr[u].r, mid + 1, r);
        pushup(tr[u]);
    }
    int query(int u, int l, int r, int ql, int qr) {
        if (!u || r < ql || l > qr || ql > qr) return INF;
        if (l >= ql && r <= qr) return tr[u].v;
        int res = INF;
        int mid = (l + r) >> 1;
        res = query(tr[u].l, l, mid, ql, qr);
        res = min(res, query(tr[u].r, mid + 1, r, ql, qr));
        #ifdef ROSHIN
        printf("l=%d, r=%d, res=%d\n", l, r, res);
        #endif
        return res;
    }
}ctr;

char s[N];
int n, q;
PII H[N];

int p[N];
set<int> S[N];

int updated[N], cnt = 0, id[N];

int find(int x) {
    if (x != p[x]) p[x] = find(p[x]);
    return p[x];
}

void update(int pos, int val) {
    if (!updated[pos]) id[cnt++] = pos;
    updated[pos] = val;
}

void Union(int x, int y) {
    x = find(x), y = find(y);
    if (SZ(S[x]) < SZ(S[y])) swap(x, y);
    p[y] = x;
    for (auto it: S[y]) {
        auto suf = S[x].lower_bound(it);
        if (suf != S[x].end()) update(it, *suf);
        if (suf != S[x].begin()) update(*prev(suf), it);
        S[x].insert(it);
    }
    S[y].clear();
}


int main() {
    // freopen("input.txt", "r", stdin);
    // freopen("output.txt", "w", stdout);
    re(n), re(q);
    scanf("%s", s + 1);
    sa.get_sa(s, n);
    sa.get_height(s);
    for (int i = 1; i <= n; i++) p[i] = i, S[i].insert(i);
    for (int i = 2; i <= n; i++) H[i - 1] = {sa.height[i], i};
    sort(H + 1, H + n);
    ctr.build(ctr.root[n], 1, n);

    for (int i = n - 1, j = n - 1; i >= 1; i--) {
        cnt = 0, tmp.clear();
        while (j && H[j].first == i) {
            int idx = H[j--].second;
            Union(sa.sa[idx], sa.sa[idx - 1]);
        }
        sort(id, id + cnt);
        for (int i = cnt - 1; i >= 0; i--) {
            tmp.pb({id[i], updated[id[i]]}), updated[id[i]] = 0;
        }
        if (tmp.empty()) 
            ctr.root[i] = ctr.root[i + 1];
        else
            ctr.modify(ctr.root[i + 1], ctr.root[i], 1, n);
        #ifdef ROSHIN
        printf("L=%d\n",i);
        for (auto t: tmp) {
            cout << t.first << " " << t.se;
        }
        cout << endl;
        #endif
    }
    while (q--) {
        int L, R;
        re(L), re(R);
        int l = 0, r = R - L;
        while (l < r) {
            int mid = (l + r + 1) >> 1;
            if (ctr.query(ctr.root[mid], 1, n, L, R - mid) <= R - mid + 1) l = mid;
            else r = mid - 1;
        }
        printf("%d\n", l);
    }
    // fclose(stdin);
    // fclose(stdout);
    return 0;
}

标签:tmp,拜神,tr,id,int,P7361,SA,sa,define
From: https://www.cnblogs.com/Roshin/p/P7361.html

相关文章

  • 收藏贴!Salesforce开发课程必看的10个Apex最佳实践
    Apex是一种强类型的,面向对象的编程语言,开发人员通过Apex表现业务逻辑来补充Salesforce平台所需的功能。Apex与Java很像,可以通过各种用户启动的事件来触发,例如记录更新,单击......
  • QSocketNotifier: Socket notifiers cannot be enabled or disabled from another(转)
    在使用Qt开发多线程、socket通讯功能时,遇到以下两个问题:QSocketNotifier:SocketnotifierscannotbeenabledordisabledfromanotherQObject:Cannotcreatechildr......
  • 字节跳动开源数据集成引擎 BitSail 的演进历程与能力解析
    导读BitSail是字节跳动开源数据集成引擎,支持多种异构数据源间的数据同步,并提供离线、实时、全量、增量场景下全域数据集成解决方案,目前支撑了字节内部和火山引擎多个客户的......
  • [Java web]-- jquery设置元素成为disabled
    对元素应用disabled和readonly属性的方法,如下:  1.readonly属性举例  $('input').attr("readonly","readonly");     //将input元素设置为readonly ......
  • Segmentation 2 -- usage
    ThesegmentationmechanismsupportedbytheIA-32architecturecanbeusedtoimplementawidevarietyof systemdesigns.Thesedesignsrangefromflatmodel......
  • RSA加密算法
    RSA加密算法5分钟了解RSA加解密算法:https://zhuanlan.zhihu.com/p/365330981验证数据完整性:私钥签名-公钥验签;消息加密:公钥加密-私钥解密;......
  • SampleClean概述
    目前阶段临近考试周,近期将在复习大四专业课的基础上,计划学习以下内容:项目内容:自动化的清洗算子框架学习(了解SampleClean和进阶版本的ActiveClean)从中理解质量评估函数设......
  • 如何确认 SAP Spartacus SSR Transfer State 已经正常工作了
    在检查一些客户项目时,我注意到一旦返回SSR响应,浏览器仍然会执行页面和组件的XHR请求。我使用的代码为:provideConfig(<StateConfig>{state:{ssrTran......
  • P4826 [USACO15FEB]Superbull S
    最大生成树,暴力加贪心;#include<bits/stdc++.h>usingnamespacestd;typedefunsignedlonglongull;constintN=2007;ullid[N];intf[N];structNode{ inti,j;......
  • saltstack服务端与客户端通信问题处理
    jenkins发布报错:ERROR:NoreturnreceivedNominionsmatchedthetarget.Nocommandwassent,nojidwasassigned.saltstack分为服务端master与客户端minion配置文......