hdu 6194 string string string 后缀数组+rmq+容斥


string string string

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 1531    Accepted Submission(s): 440


Problem Description
Uncle Mao is a wonderful ACMER. One day he met an easy problem, but Uncle Mao was so lazy that he left the problem to you. I hope you can give him a solution.
Given a string s, we define a substring that happens exactly  k  times as an important string, and you need to find out how many substrings which are important strings.
 

Input
The first line contains an integer  T  ( T100 ) implying the number of test cases.
For each test case, there are two lines:
the first line contains an integer  k  ( k1 ) which is described above;
the second line contain a string  s  ( length(s)105 ).
It's guaranteed that  length(s)2106 .
 

Output
For each test case, print the number of the important substrings in a line.
 

Sample Input
  
  
2 2 abcabc 3 abcabcabcabc
 

Sample Output
  
  
6 9
 

Source
 

Recommend
liuyiding   |   We have carefully selected several similar problems for you:   6205  6204  6203  6202  6201 
 

Statistic |  Submit |  Discuss |  Note
题意:求刚好出现k次的子串有多少个。

思路:我做这道题纯碎来学一下后缀数组怎么用。。。。首先构造好数组之后,每次枚举相邻k个排名的公共前缀,因为他要刚好出现k次,所以容斥减去出现k+1次再加上k+2次的就是贡献。。查询rmq预处理可以O(1),查询log应该也可以过。下面给代码:

#pragma comment(linker, "/STACK:102400000,102400000") 
#include<iostream>  
#include<cmath>  
#include<queue>  
#include<cstdio>  
#include<queue>  
#include<algorithm>  
#include<cstring>  
#include<string>  
#include<utility>
#include<set>
#include<map>
#include<stack>
#include<vector>
#define maxn 100005
#define inf  0x3f3f3f3f3f3f3f3f
using namespace std;
typedef long long LL;
const double eps = 1e-8;
const int mod = 1e9 + 7;
const int MAXN = 100005;
int SA[MAXN], Rank[MAXN], Height[MAXN], tax[MAXN], tp[MAXN], a[MAXN], n, m;
int q;
int f[MAXN][20];
char str[MAXN];
//rank[i] 第i个后缀的排名; SA[i] 排名为i的后缀位置; Height[i] 排名为i的后缀与排名为(i-1)的后缀的LCP  
//tax[i] 计数排序辅助数组; tp[i] rank的辅助数组(计数排序中的第二关键字),与SA意义一样。  
//a为原串  
void RSort() {
	//rank第一关键字,tp第二关键字。  
	for (int i = 0; i <= m; i++) tax[i] = 0;
	for (int i = 1; i <= n; i++) tax[Rank[tp[i]]] ++;
	for (int i = 1; i <= m; i++) tax[i] += tax[i - 1];
	for (int i = n; i >= 1; i--) SA[tax[Rank[tp[i]]] --] = tp[i]; //确保满足第一关键字的同时,再满足第二关键字的要求  
} //计数排序,把新的二元组排序。  

int cmp(int *f, int x, int y, int w) { return f[x] == f[y] && f[x + w] == f[y + w]; }
//通过二元组两个下标的比较,确定两个子串是否相同  

void Suffix() {
	//SA  
	for (int i = 1; i <= n; i++) Rank[i] = a[i], tp[i] = i;
	m = 30, RSort(); //一开始是以单个字符为单位,所以(m = 127)  

	for (int w = 1, p = 1, i; p < n; w += w, m = p) { //把子串长度翻倍,更新rank  

		//w 当前一个子串的长度; m 当前离散后的排名种类数  
		//当前的tp(第二关键字)可直接由上一次的SA的得到  
		for (p = 0, i = n - w + 1; i <= n; i++) tp[++p] = i; //长度越界,第二关键字为0  
		for (i = 1; i <= n; i++) if (SA[i] > w) tp[++p] = SA[i] - w;

		//更新SA值,并用tp暂时存下上一轮的rank(用于cmp比较)  
		RSort(), swap(Rank, tp), Rank[SA[1]] = p = 1;

		//用已经完成的SA来更新与它互逆的rank,并离散rank  
		for (i = 2; i <= n; i++) Rank[SA[i]] = cmp(tp, SA[i], SA[i - 1], w) ? p : ++p;
	}
	//离散:把相等的字符串的rank设为相同。  
	//LCP  
	int j, k = 0;
	for (int i = 1; i <= n; Height[Rank[i++]] = k)
		for (k = k ? k - 1 : k, j = SA[Rank[i] - 1]; a[i + k] == a[j + k]; ++k);
	//这个知道原理后就比较好理解程序  
}

void Init() {
	scanf("%d%s", &q, str);
	n = strlen(str);
	for (int i = 0; i < n; i++) a[i + 1] = str[i] - 'a' + 1;
	a[n + 1] = 0;
}
void rmq(){
	for (int i = n; i > 1; i--)
	{
		for (int j = 0; i + (1 << j) - 1 <= n; j++)
		{
			if (!j)f[i][j] = Height[i];
			else f[i][j] = min(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
		}
	}
}
int getans(int l, int r){
	if (l > r)
		return n - SA[r] + 1;
	int k = log2(r - l + 1);
	return min(f[l][k], f[r - (1 << k) + 1][k]);
}
int main() {
	int t;
	scanf("%d", &t);
	while (t--){
		memset(SA, 0, sizeof(SA));
		memset(Height, 0, sizeof(Height));
		memset(Rank, 0, sizeof(Rank));
		memset(tax, 0, sizeof(tax));
		memset(tp, 0, sizeof(tp));
		Init();
		Suffix();
		rmq();
		LL ans = 0;
		for (int i = 1; i <= n - q + 1; i++){
			ans += getans(i + 1, i + q - 1);
			if (i > 1)
				ans -= getans(i, i + q - 1);
			if (i + q <= n)
				ans -= getans(i + 1, i + q);
			if (i > 1 && i + q <= n)
				ans += getans(i, i + q);
		}
		printf("%lld\n", ans);
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值