PAT (Top Level) Practice: 1005 Programming Pattern

1005 Programming Pattern (35 分)

题目链接(https://pintia.cn/problem-sets/994805148990160896/problems/994805154748940288)

问题描述

Programmers often have a preference among program constructs. For example, some may prefer if ( 0 = = a ) (0==a) (0==a), while others may prefer if ( ! a ) (!a) (!a). Analyzing such patterns can help to narrow down a programmer’s identity, which is useful for detecting plagiarism.

Now given some text sampled from someone’s program, can you find the person’s most commonly used pattern of a specific length?

输入格式

Each input file contains one test case. For each case, there is one line consisting of the pattern length $N $ ( 1 ≤ N ≤ 1048576 ) (1≤N≤1048576) (1N1048576), followed by one line no less than N N N and no more than 1048576 1048576 1048576 characters in length, terminated by a carriage return ‘\n’. The entire input is case sensitive.

输出格式

For each test case, print in one line the length-N substring that occurs most frequently in the input, followed by a space and the number of times it has occurred in the input. If there are multiple such substrings, print the lexicographically smallest one.

Whitespace characters in the input should be printed as they are. Also note that there may be multiple occurrences of the same substring overlapping each other.

题目概述

给定一个 N N N,一个长度不超过 1048576 1048576 1048576 的字符串,要求输出出现次数最多的子串以及出现次数

代码1—哈希

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll k = 131;  //将原字符串转换为k进制数
const ll mod = 5e6 + 7;  //取模数,即Tablesize大小
struct yyy {
    int sum, l; //记录该储存位置上重复字符串个数,字符串第一次出现区间的左端点
};
yyy Hash[mod];  //记录每个字符串映射值对应字符串的个数及左端点
int n, len, ans, L;  //L表示答案的左端点
string s;

ll pow_mod(ll a, ll x){  //快速幂
    ll res = 1;
    while(x) {
        if(x & 1) res = (res * a) % mod;
        a = (a * a) % mod;
        x >>= 1;
    }
    return res;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);  //cin/cout加速读入,可忽略
    cin >> len;  //读入目标字符串长度
    getline(cin, s);  //吸收换行符
    getline(cin, s);  //读取完整字符串
    n = s.length();  //求该字符串长度

    // hash算法
    ll tmp = 0;  //用于计算长度为len的字符串的映射值
    for(int i = 0;i < len;i++) {
        tmp = ((tmp * k) % mod + s[i]) % mod;
        //把第一个长度为len的字符串转换为k进制
    }
    Hash[tmp].sum++;  //将第一个长度为len的字符串对应哈希值出现次数+1
    Hash[tmp].l = 0;  //第一个长度为len的字符串左端点下标为0
    ans = 1;  //答案至少为1
    L = 0;  //答案左端点初始化为0
    ll high = pow_mod(k, len - 1);
    //high = k的(len-1)次方,即进制转换中最高位需要乘的值
    for(int i = len;i < n;i++) {
        tmp = (tmp + mod - (s[i - len] * high % mod)) % mod;
        //移动窗口,右端点有新增字符,需要把最左边的字母从串中移除,即减去左端点的贡献值
        tmp = ((tmp * k) % mod + s[i]) % mod;  //秦九韶算法计算进制,把新增字符加进来
        Hash[tmp].sum++;  //对应哈希值出现次数+1
        if(Hash[tmp].sum == 1) Hash[tmp].l = i - len + 1;
        //如果这个串第一次出现,记录该串左端点下标
    }
    for(int i = 0;i < mod;i++) {
        if(Hash[i].sum > ans) {  //如果出现次数大于ans
            ans = Hash[i].sum;  //更新ans和L
            L = Hash[i].l;
        } else if(Hash[i].sum == ans) {  //如果出现次数等于ans,需判断字典序是否最小
            int idx = Hash[i].l;
            for(int j = L;j <= L + len - 1;j++) {  //逐位比较,如果新串字典序更小则更新L
                if(s[j] < s[idx]) break;
                if(s[j] > s[idx]) {
                    L = Hash[i].l;
                    break;
                }
                idx++;
            }
        }
    }
    cout << s.substr(L, len) << ' ' << ans;  //输出
    return 0;
}

代码2——后缀数组

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e6 + 5;
string s;
int i,n,m,len;
int c[N],x[N],y[N];       // c数组是桶
int sa[N],rk[N],height[N];  
//sa为后缀数组 rk为排名数组  height为 后缀i和i-1的公共前缀长度数组
void get_SA() {
	int i,k,num;
	for(i = 0;i < n;i++) c[x[i] = s[i]]++; //x[i]是第i个元素的第一关键字 
    for(i = 1;i < m;i++) c[i] += c[i-1]; 
    //做c的前缀和,我们就可以得出每个关键字最多是在第几名 
    for(i = n - 1;i >= 0;i--) sa[--c[x[i]]] = i;   //第一次赋值后缀数组 
    for(k = 1;k <= n;k *= 2) {    // 倍增思路  每一次的 k = k * 2
        num = 0;
        for(i = n - k;i < n;i++) y[num++] = i; 
		//y[i]表示第二关键字排名为i的数,第一关键字的位置 
        //第n-k+1到第n位是没有第二关键字的 所以排名在最前面 
        for(i = 0;i < n;i++) if(sa[i] >= k) y[num++] = sa[i] - k;
        //排名为i的数 在数组中是否在第k位以后
		//如果满足(sa[i]>k) 那么它可以作为别人的第二关键字,就把它的第一关键字的位置添加进y就行了
		//所以i枚举的是第二关键字的排名,第二关键字靠前的先入队 
        for(i = 0;i < m;i++) c[i] = 0;    //初始化c桶 
        for(i = 0;i < n;i++) c[x[y[i]]]++;
        //因为上一次循环已经算出了这次的第一关键字 所以直接加就行了 
        for(i = 1;i < m;i++) c[i] += c[i-1];  //第一关键字排名为1~i的数有多少个 
        for(i = n - 1;i >= 0;i--) sa[--c[x[y[i]]]] = y[i];
        //因为y的顺序是按照第二关键字的顺序来排的 
        //第二关键字靠后的,在同一个第一关键字桶中排名越靠后 
        //基数排序 
        swap(x,y);  //要生成新的x时要用到旧的x,就把旧的x赋值到y 
        x[sa[0]] = 0;num = 1;
        for(i = 1;i < n;i++) {
            x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]) ? num - 1 : num++;
            //因为sa[i]已经排好序了,所以可以按排名枚举,生成下一次的第一关键字 
        }
        if(num == n) break;
        m = num;  //更新 m
    }
}
void get_height() {
    int k = 0;      //表示公共前缀长度 
    for(int i = 0;i < n;i++) rk[sa[i]] = i;     //rk表示该后缀的排名 
    for(int i = 0;i < n;i++) {
        if (rk[i] == 0) continue;  //第一名height为0 
        if (k) k--;
        int j = sa[rk[i] - 1];     // j的排名为当前后缀排名的前一名后缀的开头 
        while (j + k < n && i + k < n && s[i + k] == s[j + k]) k++;  
        height[rk[i]] = k;      // 公共前缀数组 
    }
}
int main() {
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);                   //加速 cin 读入 
	cin >> len;
	getline(cin,s);
	getline(cin,s);
	n = s.length();
	m = 130;   // m表示字符个数,ascll 128个,所以要 >128 
    get_SA();    //得到后缀数组  
    get_height();   //得到公共前缀长度数组 
    int maxn = 0;
    int cnt = 0,id = -1,x;
    height[n] = 0;   // 设置哨兵  不用退出循环之后再判断  
    for(i = 0;i <= n;i++) {
    	if(height[i] >= len) {
    		if(cnt == 0)x = i;   // x 记录这个长段的开头 
    		cnt++;  //计数 
    	}
    	else {
    		if(cnt > maxn) {   // 如果 cnt > maxn就更新答案  
    			maxn = cnt;
    			id = x;
    		}
    		cnt = 0;    //计数器清零 
    	}
    }
    if(id == -1) {      // 如果 id == -1   就表明最多出现的次数只有一次 所以要遍历后缀数组 
    	int x = n - len;
    	for(i = 0;i < n;i++) {
    		if(sa[i] <= x)break;
    	}
    	id = i;
    }
    cout << s.substr(sa[id],len) << " " << maxn + 1;
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

he_69

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值