【TJOI2019】甲苯先生和大中锋的字符串

题目链接:https://www.luogu.com.cn/problem/P5341

题目大意:给定 \(T\) 个字符串, 分别求出这 \(T\) 个字符串中所有恰好出现 \(k\) 次的子串中 , 出现过最多次数的长度的最大值

solution

对每个字符串 , 先求出它的 \(height\) 数组

考虑恰好出现 \(k\) 次的子串 \(s\) , 对于 \(height\) 数组中连续的 \(h[i] ... h[i + k - 1]\) , 其必然满足 \(len(s) < min \left\{h[i] , h[i + 1] , ... ,h[i + k - 1]\right\}\) , 同时 \(s\) 的出现次数不应超过 \(k\) , 因此 \(len(s) > max\left\{h[i - 1] , h[i + k]\right\}\) , 不难看出对于每 \(k\) 个数 , 满足要求的 \(s\) 的长度一定连续 , 于是可以用差分进行区间加 , 统计各个长度出现的次数 , 最后遍历找出最大值即可

时间复杂度: \(O(T \times nlogn)\)

code

#include<bits/stdc++.h>
using namespace std;
template <typename T> inline void read(T &FF) {
    int RR = 1; FF = 0; char CH = getchar();
    for(; !isdigit(CH); CH = getchar()) if(CH == '-') RR = -RR;
    for(; isdigit(CH); CH = getchar()) FF = FF * 10 + CH - 48;
    FF *= RR;
}
inline void file(string str) {
    freopen((str + ".in").c_str(), "r", stdin);
    freopen((str + ".out").c_str(), "w", stdout);
}
const int N = 1e5 + 10;
int tax[N], n, m = 'z', rk[N], xt[N], sa[N], ht[N], ki, pre[N];
char ch[N];
void get_sa() {
    memset(tax, 0, sizeof(tax));
    for(int i = 0; i <= n; i++) ht[i] = rk[i] = xt[i] = sa[i] = 0;
    for(int i = 1; i <= n; i++) tax[rk[i] = ch[i]]++;
    for(int i = 1; i <= m; i++) tax[i] += tax[i - 1];
    for(int i = n; i >= 1; i--) sa[tax[rk[i]]--] = i;
    for(int k = 1; k <= n; k <<= 1) {
        int now = 0;
        for(int i = n - k + 1; i <= n; i++) xt[++now] = i;
        for(int i = 1; i <= n; i++)
            if(sa[i] > k) xt[++now] = sa[i] - k;
        for(int i = 1; i <= m; i++) tax[i] = 0;
        for(int i = 1; i <= n; i++) tax[rk[i]]++;
        for(int i = 1; i <= m; i++) tax[i] += tax[i - 1];
        for(int i = n; i >= 1; i--) sa[tax[rk[xt[i]]]--] = xt[i];
        swap(xt, rk); now = rk[sa[1]] = 1;
        for(int i = 2; i <= n; i++)
            rk[sa[i]] = xt[sa[i]] == xt[sa[i - 1]] && xt[sa[i] + k] == xt[sa[i - 1] + k] ? now : ++now;
        m = now; if(n == m) return;
    }
}
void get_height() {
    for(int i = 1; i <= n; i++) rk[sa[i]] = i;
    int j = 0;
    for(int i = 1; i <= n; i++) {
        if(rk[i] == 1) continue;
        if(j != 0) j--;
        while(i + j <= n && sa[rk[i] - 1] + j <= n && ch[sa[rk[i] - 1] + j] == ch[i + j]) j++;
        ht[rk[i]] = j;
    }
}
int main() {
    //file("");
    int T;
    read(T);
    while(T--) {
        cin >> ch + 1;
        n = strlen(ch + 1); m = 'z';
        get_sa(), get_height();
        cin >> ki; ht[n + 1] = ht[0] = 0;
        for(int i = 0; i <= n + 1; i++) pre[i] = 0;
        deque<int> qi; int ans = 0;
        for(int i = 1; i <= ki; i++) {
            while(!qi.empty() && ht[i] <= ht[qi.front()]) qi.pop_front();
            qi.push_front(i);
        }
        for(int i = ki; i <= n; i++) {
            if(i - qi.back() + 1 >= ki) qi.pop_back();
            while(!qi.empty() && ht[i] <= ht[qi.front()]) qi.pop_front();
            qi.push_front(i);
            int lmax = 0, lmin = max(ht[i + 1], ht[i - ki + 1]);
            if(ki == 1) lmax = n - sa[i + ki - 1] + 1;
            else lmax = ht[qi.back()];
            if(lmax > lmin) pre[lmin + 1]++, pre[lmax + 1]--;
        }
        for(int i = 1; i <= n; i++) pre[i] += pre[i - 1];
        for(int i = n; i >= 1; i--)
            if(pre[i] > pre[ans]) ans = i;
        if(ans == 0) ans = -1;
        cout << ans << endl;
    }
    return 0;
}

原文地址:https://www.cnblogs.com/magicduck/p/12238966.html

时间: 2024-08-30 18:33:47

【TJOI2019】甲苯先生和大中锋的字符串的相关文章

[TJOI2019]甲苯先生和大中锋的字符串

有个叫asuldb的神仙来嘲讽我 说这题SAM水题,而且SA过不了 然后我就用SA过了 显然是一个Height数组上长为k的滑块,判一下两边,差分一下就可以了 #include"cstdio" #include"cstring" #include"iostream" #include"algorithm" using namespace std; const int MAXN=1e5+5; int n,T,mx,hd,tl;

p5341 [TJOI2019]甲苯先生和大中锋的字符串

分析 TJOI白给题 建出sam,对于每个点如果它的子树siz和等于k 那么对于这个满足的点它有贡献的长度一定是一个连续区间 直接差分即可 代码 #include<bits/stdc++.h> using namespace std; int n,k,mx,ans,d[100100]; char s[100100]; struct SAM { int mp[200100][30],fa[200100],ed,ccnt,len[200100],siz[200100]; int head[2001

【题解】Luogu P5340 [TJOI2019]大中锋的游乐场

原题传送门 没想到省选也会出这种题??! 实际就是一个带有限制的最短路 因为\(k<=10\),所以我们珂以暴力将每个点的权值分为[-k,k],为了方便我们珂以转化成[0,2k],将汉堡的权值记为1,可乐的权值记为-1,最短路即可,如果发现不合理的就果断扔掉即可(不知道有没有好事之徒用SPFA写) #include <bits/stdc++.h> #define N 10005 #define pi pair<int,int> #define getchar nc using

【题解】Luogu P5337 [TJOI2019]甲苯先生的字符串

原题传送门 我们设计一个\(26*26\)的矩阵\(A\)表示\(a~z\)和\(a~z\)是否能够相邻,这个矩阵珂以由\(s1\)得出.答案显然是矩阵\(A^{len_{s2}-1}\)的所有元素之和,矩阵快速幂即可 #include <bits/stdc++.h> #define ll long long #define mod 1000000007 using namespace std; inline void write(register int x) { if(!x)putchar

第十一届“蓝狐网络杯”湖南省大学生计算机程序设计竞赛 B - 大还是小? 字符串水题

B - 大还是小? Time Limit:5000MS     Memory Limit:65535KB     64bit IO Format: Description 输入两个实数,判断第一个数大,第二个数大还是一样大.每个数的格式为: [整数部分].[小数部分] 简单起见,整数部分和小数部分都保证非空,且整数部分不会有前导 0.不过,小数部分的最 后可以有 0,因此 0.0 和 0.000 是一样大的. Input 输入包含不超过 20 组数据.每组数据包含一行,有两个实数(格式如前所述)

大数据阶乘——字符串乘法器

char s[1001]; //字符串s存储乘法得到的大数字,s[0]代表低位 int GetLength()//返回s的最大不为空的元素下标 { int i; for(i=1000; i>=0; i--) { if(s[i]!='0') { break; } } return i; } void cal(int k)//计算大数字s和小数字k相乘 { int add=0;//进位数 int i; int temp; for(i=0; i<=GetLength(); i++)// { temp

2015福富福大笔试——实现字符串右移

前两天参加了福富在福大的宣讲会,并且参加了笔试,最后一道大题,这里讲一下当时我的解法,大概的题意是这个样子的,只能使用c的库,实现一个函数void MakeString(char *pStr,int n)(ps:这里的函数名是我现在取的,想不起来考题给的是什么了),函数要求是以'\0'结尾的字符串pStr,一个需要右移的字符个数n,实现类似输入这样MakeString("abcdefghi",2),字符串右移后变成:hiabcdeg: 以下给出我当时的解法,希望各位看官有什么意见或者建

[lintcode][美国大公司][1.字符串处理]

两个字符串是变位词 1 class Solution: 2 """ 3 @param s: The first string 4 @param b: The second string 5 @return true or false 6 """ 7 def anagram(self, s, t): 8 if len(s) == len(t): 9 d = {} 10 for i in range(len(s)): # full scan on e

luogu P5338 [TJOI2019]甲苯先生的滚榜

传送门 首先,排名系统,一看就知道是原题,可以上平衡树来维护 然后考虑一种比较朴素的想法,因为我们要知道排名在一个人前面的人数,也就是AC数比他多的人数+AC数一样并且罚时少的人数,所以考虑维护那两个东西.AC数更多的人数显然可以直接上树状数组.后者的话可以对每一种AC数开值域线段树,存每个罚时有多少人,注意到罚时之和不会超过\(1.5*10^6\),所以动态开点线段树可以轻松解决.然后每次有个人AC数和罚时改变就先在原来的位置-1,然后在新位置+1.每次询问就是树状数组上AC数\(>\)当前A