2019 CCPC 网络赛第三题 K-th occurrence 后缀数组+划分树+ST表+二分

题意:给你一个长度为n的字符串,每次询问给出三个数:L , R , K,表示原串 L 到 R 的子串在原串第K次出现的首字母的位置

解题思路:对子串的大量操作,不难想到后缀数组(后缀树/后缀自动机不会,所以没想到),注意到子串s[L.....R]必然是某一个后缀的前缀,所以所有前缀是该子串的后缀的排名(即rank数组的值)必定连续,也就是说在后缀数组(sa数组)中,下标是连续的,那么就是求区间第K大了(因为sa数组的值代表的是在字符串中的位置)(这里区间第K大我用划分树求),至于这一段区间的起点和终点,可以用二分求,因为 rank[ L ] 必定是所求区间中的一个值,那么就可以以rank[ L ]为中心,左右分别二分向外扩展区间,二分的check函数(判断成立条件),可以求出当前位置的后缀和L开始的后缀的LCP(最长公共前缀),判断是否大于等于(R - L + 1)即可,自然语言比较无力,直接看代码吧=。=

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100010;
int n;

/***********************后缀数组****************************/
// x[i]表示第i个字符开头的后缀在所有后缀中的排名  sa[i]表示排名为i的后缀开头字符的位置
int sa[maxn], x[maxn], c[maxn], y[maxn], height[maxn];
;
char s[maxn];
void SA() //O(nlogn) 倍增求后缀数组
{
    int m = 128;
    for (int i = 0; i <= m; i++)
        c[i] = 0;
    for (int i = 1; i <= n; i++)
        c[x[i] = s[i]]++;
    for (int i = 1; i <= m; i++)
        c[i] += c[i - 1];
    for (int i = n; i >= 1; i--)
        sa[c[x[i]]--] = i;

    for (int k = 1; k <= n; k <<= 1)
    {
        int p = 0;
        for (int i = 0; i <= m; i++)
            y[i] = 0;
        for (int i = n - k + 1; i <= n; i++)
            y[++p] = i;
        for (int i = 1; i <= n; i++)
            if (sa[i] > k)
                y[++p] = sa[i] - k;

        for (int i = 0; i <= m; i++)
            c[i] = 0;
        for (int i = 1; i <= n; i++)
            c[x[y[i]]]++;
        for (int i = 1; i <= m; i++)
            c[i] += c[i - 1];
        for (int i = n; i >= 1; i--)
            sa[c[x[y[i]]]--] = y[i];

        swap(x, y);
        x[sa[1]] = 1;
        p = 1;
        for (int i = 2; i <= n; ++i)
            x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]) ? p : ++p;
        if (p >= n)
            break;
        m = p;
    }
}
void get_height() //求height数组
{
    int k = 0;
    //for (int i=1; i<=n; ++i) rk[sa[i]]=i; x数组即为rank数组
    for (int i = 1; i <= n; ++i)
    {
        if (x[i] == 1)
            continue;
        if (k)
            --k;
        int j = sa[x[i] - 1];
        while (j + k <= n && i + k <= n && s[i + k] == s[j + k])
            ++k;
        height[x[i]] = k;
    }
}
/*******************************************************************/

/**************************划分树开始**************************/
int tree[30][maxn];   //表示每层每个位置的值
int sorted[maxn];     //已经排序的数
int toleft[30][maxn]; //toleft[p][i]表示第i层从1到i有多少个数分入左边

void build(int l, int r, int dep)
{
    if (l == r)
        return;
    int mid = (l + r) >> 1;
    int same = mid - l + 1;      //表示等于中间值而且被分入左边的个数  先初始化为左边的个数  之后再减
    for (int i = l; i <= r; i++) //是L  不是one
    {
        if (tree[dep][i] < sorted[mid])
            same--;
    }
    int lpos = l;
    int rpos = mid + 1; //本节点的两个孩子节点的开头
    for (int i = l; i <= r; i++)
    {
        if (tree[dep][i] < sorted[mid]) //比中间的数小,分入左边
            tree[dep + 1][lpos++] = tree[dep][i];
        else if (tree[dep][i] == sorted[mid] && same > 0)
        {
            tree[dep + 1][lpos++] = tree[dep][i];
            same--;
        }
        else //比中间值大分入右边
            tree[dep + 1][rpos++] = tree[dep][i];
        toleft[dep][i] = toleft[dep][l - 1] + lpos - l; //从1到i放左边的个数
    }
    build(l, mid, dep + 1);
    build(mid + 1, r, dep + 1);
}

//查询区间第k大的数,[L,R]是大区间,[l,r]是要查询的小区间
int query(int L, int R, int l, int r, int dep, int k)
{
    if (l == r)
        return tree[dep][l];
    int mid = (L + R) >> 1;
    int cnt = toleft[dep][r] - toleft[dep][l - 1]; //[l,r]中位于左边的个数
    if (cnt >= k)
    {
        //L+要查询的区间前被放在左边的个数
        int newl = L + toleft[dep][l - 1] - toleft[dep][L - 1];
        //左端点加上查询区间会被放在左边的个数
        int newr = newl + cnt - 1;
        return query(L, mid, newl, newr, dep + 1, k);
    }
    else
    {
        int newr = r + toleft[dep][R] - toleft[dep][r];
        int newl = newr - (r - l - cnt);
        return query(mid + 1, R, newl, newr, dep + 1, k - cnt);
    }
}
/***************************划分树结束***********************************/

inline int read()
{
    int sgn = 1;
    int cnt = 0;
    char ch = getchar();
    while (ch < ‘0‘ || ch > ‘9‘)
    {
        if (ch == ‘-‘)
            sgn = -sgn;
        ch = getchar();
    }
    while (‘0‘ <= ch && ch <= ‘9‘)
    {
        cnt = cnt * 10 + (ch - ‘0‘);
        ch = getchar();
    }
    return sgn * cnt;
}

/****************************   S  T  表   ****************************/
int g[maxn][20]; //区间最小
void ST_prewoek()
{
    for (int i = 1; i <= n; i++)
    {
        g[i][0] = height[i];
    }
    for (int i = 1, imax = log2(n); i <= imax; i++)
    {
        for (int j = 1; j + (1 << i) - 1 <= n; j++) //注意j的右端点为j+(1<<i)-1,-1是因为要包含j自己
        {
            g[j][i] = min(g[j][i - 1], g[j + (1 << i - 1)][i - 1]);
        }
    }
}

int ST_query(int l, int r) //求[l,r]中的最小值
{
    int k = log2(r - l + 1);
    return min(g[l][k], g[r - (1 << k) + 1][k]);
}
/************************************************************/

bool check(int l, int r, int len) //二分判断函数
{
    if (l == r)
        return true;
    l = x[l];
    r = x[r];
    if (l > r)
        swap(l, r);
    if (ST_query(l + 1, r) > len)
        return true;
    return false;
}

int main()
{
    int t, Q;
    scanf("%d", &t);
    while (t--)
    {
        memset(tree, 0, sizeof(tree));
        scanf("%d%d", &n, &Q);
        scanf("%s", s + 1);
        SA();
        get_height();
        for (int i = 1; i <= n; i++)
        {
            tree[0][i] = sorted[i] = sa[i];
        }
        sort(sorted + 1, sorted + n + 1);
        build(1, n, 0);
        ST_prewoek();
        int l, r, k;
        while (Q--)
        {
            l = read();
            r = read();
            k = read();
            int rk = x[l], len = r - l;
            int tl = 1, tr = rk;
            int nl = rk, nr = rk;
            while (tl <= tr)
            {
                int mid = (tl + tr) / 2;
                if (check(sa[mid], l, len))
                {
                    nl = mid;
                    tr = mid - 1;
                }
                else
                {
                    tl = mid + 1;
                }
            }
            tl = rk;
            tr = n;
            while (tl <= tr)
            {
                int mid = (tl + tr) / 2;
                if (check(sa[mid], l, len))
                {
                    nr = mid;
                    tl = mid + 1;
                }
                else
                {
                    tr = mid - 1;
                }
            }
            if (nr - nl + 1 < k)
            {
                puts("-1");
            }
            else
            {
                printf("%d\n", query(1, n, nl, nr, 0, k));
            }
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/Zeronera/p/11403335.html

时间: 2025-01-12 09:03:31

2019 CCPC 网络赛第三题 K-th occurrence 后缀数组+划分树+ST表+二分的相关文章

2019 CCPC网络赛

一到网络赛,大家都是东亚之光 1001 00:23:46 solved by hl 签到 枚举一下位就行了 #include <map> #include <set> #include <ctime> #include <cmath> #include <queue> #include <stack> #include <vector> #include <string> #include <bitset

2019 南昌网络赛icpc I题 cdq分治或分块

题意:给你一个数组,然后每次有两种操作,操作一是修改数组里的数,操作二是查询区间[ l , r ] 里有多少个子区间满足以下条件:1.子区间内的数全部相同.2.子区间内的数在x到y之间.3.子区间得是不能延伸的. 题目链接:https://nanti.jisuanke.com/t/41356 题解:首先转化问题,设 b[ i ] = a[i]==a[i-1] ? 0 : a[i],然后问题就变成了问询区间内有多少个x到y之间的数.(注意左端点特判)这不就是主席树....带修改...好,树状数组加

ACM-ICPC 2019南昌网络赛I题 Yukino With Subinterval

ACM-ICPC 2019南昌网络赛I题 Yukino With Subinterval 题目大意:给一个长度为n,值域为[1, n]的序列{a},要求支持m次操作: 单点修改 1 pos val 询问子区间中某个值域的数的个数,连续的相同数字只记为一个.(即统计数字段的个数) 2 L R x y 数据范围: 1 ≤ n,m ≤ 2×10^5 1 ≤ a[i] ≤ n 解题思路: 连续重复的数字只记一次.所以考虑将每个数字段除第一个出现外的数字都删去(记为0).在读入操作的时候暴力模拟,同时维护

2016 CCPC 网络赛 B 高斯消元 C 树形dp(待补) G 状压dp+容斥(待补) H 计算几何

2016 CCPC 网络赛 A - A water problem 水题,但读题有个坑,输入数字长度很大.. B - Zhu and 772002 题意:给出n个数(给出的每个数的质因子最大不超过2000),选出多个数相乘得b.问有多少种选法让b 为完全平方数. tags:高斯消元,求异或方程组解的个数.   好题 每个数先素数分解开.  对于2000以内的每个素数p[i],这n个数有奇数个p[i]则系数为1,偶数个则系数为0,最后n个数的p[i]系数异或和都要为0才会使得最后的积为完全平方数.

2018 CCPC网络赛

2018 CCPC网络赛 Buy and Resell 题目描述:有一种物品,在\(n\)个地点的价格为\(a_i\),现在一次经过这\(n\)个地点,在每个地点可以买一个这样的物品,也可以卖出一个物品,问最终赚的钱的最大值. solution 用两个堆来维护,一个堆维护已经找到卖家的,一个堆维护还没找到卖家的. 对于第\(i\)个地点,在已经找到卖家的堆里找出卖的钱的最小值,如果最小值小于\(a_i\),则将卖家换成\(i\),然后将原来的卖家放到没找到卖家的那里:如果最小值对于\(a_i\)

hdu6153 A Secret CCPC网络赛 51nod 1277 KMP

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=6153 题意: 给出两个字符串S1,S2,求S2的所有后缀在S1中出现的次数与其长度的乘积之和. 思路: CCPC网络赛题解: https://post.icpc-camp.org/d/714-ccpc-2017 http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1277   是一样的 将s1,s2翻转,转化为求前缀在s1中出

2019徐州网络赛 XKC&#39;s basketball team 线段树

网址:https://nanti.jisuanke.com/t/41387 题意: 大家好,我是训练时长两年半的个人练习生蔡徐坤,我的爱好是唱,跳,rap,篮球. 给出一段长度为$n,(n \leq 1e5)$的序列,对每一个数,求出它和它后面比它大$m$的数中间夹着的数的数量,没有输出$-1$. 题解: 直接建线段树,维护最大值,然后查询时对第$i$个数,搜索区间$[i,n]$之中大于$num[i]+m$的值的位置的最大值,具体操作是先限定区间,然后求出所有合法位置,取最大值,如果搜索不到则返

树形DP CCPC网络赛 HDU5834 Magic boy Bi Luo with his excited tree

1 // 树形DP CCPC网络赛 HDU5834 Magic boy Bi Luo with his excited tree 2 // 题意:n个点的树,每个节点有权值为正,只能用一次,每条边有负权,可以走多次,问从每个点出发的最大获益 3 // 思路: 4 // dp[i]: 从i点出发回到i点的最大值 5 // d[i][0] 从i点出发不回来的最大值 6 // d[i][1] 从i点出发取最大值的下一个点 7 // d[i][2] 从i点出发取次大值 8 // dfs1处理出这四个 9

2019杭电多校&amp;CCPC网络赛&amp;大一总结

多校结束了, 网络赛结束了.发现自己还是太菜了,多校基本就是爆零和签到徘徊,第一次打这种高强度的比赛, 全英文,知识点又很广,充分暴露了自己菜的事实,发现数学还是很重要的.还是要多刷题,少玩游戏. 网络赛也打的不好, 开场写01,先是思路错,再是没考虑特判,直接罚时爆炸,再是写06,题意又看错,看了很久.其中队友过07, 我看08,队友03,08先乱写了个优先队列直接t,然后边吃边想,想到正解,忘记加换行... 最后看02, 也没写出来,队友03也是没调出来, 口上说着主攻数据结构,连想的算法都