首页 > 其他分享 >P7114 [NOIP2020] 字符串匹配

P7114 [NOIP2020] 字符串匹配

时间:2024-04-24 21:12:41浏览次数:23  
标签:后缀 log P7114 int 复杂度 vis 字符串 NOIP2020 now

P7114 [NOIP2020] 字符串匹配

看到循环部分 \(AB\),自然想要去枚举它,并且用哈希。开始想到的是倍增+hash求出最长循环的右端点,复杂度是 \(O(n\log n)\),结果不好写,没写出来。

我们先思考找到右端点怎么计算贡献。最朴素的,我们再枚举前缀 \(ABAB\cdots AB\),容易预处理出后缀出现奇数次的字符数量,所以当前贡献即为 \(AB\) 能有多少种分割使得 \(A\) 中出现奇数次的字符数量小于后缀的。看出可以把值域看作一维,树状数组单点插入,前缀和查询即可。

看到题解关于复杂度的一个结论 \(\sum\limits_{i=1}^n\dfrac{n}{i}\approx n\ln n\le n\log n\)。

我们直接枚举比倍增还快,这样代码变简单,复杂度降为 \(O(n(\ln n+\log 26))\),还是不保险。

我们考虑如何简化贡献的计算,发现对于后缀 \(ABABC\),它出现奇数次的字符数量和 \(C\) 是一样的;而 \(ABABABC\) 与 \(ABC\) 是一样的。所以我们将后缀分为奇偶两部分,每部分的贡献只需要在树状数组求出的值上乘一个系数(即能出现多少次奇偶相同的)。

复杂度为 \(O(n\ln n+\log 26)\)。

#include <bits/stdc++.h>
typedef long long ll;
int read() {
	int x = 0, f = 1;
	char c = getchar();
	while(!isdigit(c)) {
		if(c == '-') f = -1;
		c = getchar();
	}
	while(isdigit(c)) {
		x = (x << 3) + (x << 1) + (c - '0');
		c = getchar();
	} 
	return x * f;
}
const ll p = 1331, mod = 13333331;
ll t, n, h[2000010], pw[2000010], suf[2000010], vis[30], ans, c[30];
char s[2000010];
ll hsh(int l, int r) {
	return (h[r] - h[l - 1] * pw[r - l + 1] % mod + mod) % mod;
}
ll lowbit(ll x) {
	return x & (-x);
}
void update(int x, ll y) {
	for(int i = x; i <= 27; i += lowbit(i)) {
		c[i] += y;
	}
}
ll query(int x) {
	ll ret = 0;
	for(int i = x; i; i -= lowbit(i)) {
		ret += c[i];
	}
	return ret;
}
void Solve() {
	t = read();
	pw[0] = 1;
	for(int i = 1; i <= 2000000; i++) {
		pw[i] = pw[i - 1] * p % mod; 
	}
	while(t--) {
		ans = 0;
		std::cin >> s + 1;
		n = strlen(s + 1);
		for(int i = 1; i <= n; i++) {
			h[i] = (h[i - 1] * p + s[i]) % mod;
		}
		int now = 0;
		memset(vis, 0, sizeof(vis));
		memset(c, 0, sizeof(c));
		for(int i = n; i >= 1; i--) {
			vis[s[i] - 'a']++;
			if(vis[s[i] - 'a'] % 2 == 1) now++;
			else now--;
			suf[i] = now;
		}
		memset(vis, 0, sizeof(vis));
		now = 0;
		for(int i = 1; i < n; i++) {
			int r = i;
			for(int j = i; j <= n; j += i) {
				// std::cout << i << " " << j << " " << h[i] << " " << hsh(j - i + 1, j) << "\n";
				if(h[i] != hsh(j - i + 1, j) || j == n) {
					break;
				}
				else r = j;
			}
//			std::cout << i << " " << r << " ";
			int res1 = query(suf[r + 1] + 1), res2 = query(suf[r - i + 1] + 1);
//			std::cout << res1 << "\n";
			if(i != 1) {
				if(r / i == 1) {
					ans += res1;
				}
				else {
					if(((r / i) % 2) == 0) {
						ans += (r / i / 2) * res1;
					}
					else ans += (r / i / 2 + 1) * res1;
					if(((r / i + 1) % 2) == 0) {
						ans += ((r / i + 1) / 2 - 1) * res2; 
					}
					else ans += ((r / i + 1) / 2) * res2;
				}
			}
			// std::cout << r << "\n";
			vis[s[i] - 'a']++;
			if(vis[s[i] - 'a'] % 2 == 1) now++;
			else now--;
			update(now + 1, 1);
		}
		std::cout << ans << "\n";
	}
}

int main() {
	
	Solve();

	return 0;
}

标签:后缀,log,P7114,int,复杂度,vis,字符串,NOIP2020,now
From: https://www.cnblogs.com/FireRaku/p/18092161

相关文章

  • HJ23 删除字符串中出现次数最少的字符
    利用list的排序来得到最小次数的字符,其中需要注意对map做深拷贝!卡了很久,因为不知道如何处理最小这一点publicclassMain{publicstaticvoidmain(String[]args){Scannerin=newScanner(System.in);//注意hasNext和hasNextLine的区别......
  • delphi Unix时间戳 转yyyy-mm-dd hh:mm:ss 格式字符串
    functionUnixTimeStampToDateTimeStr(UnixTimeStamp:Int64):string;varDateTimeValue:TDateTime;begin//第二个参数默认为true,设置为false,会默认以本地时区来+8小时,因为mysql里村的utc时间秒数DateTimeValue:=UnixToDateTime(UnixTimeStampdiv1000,False......
  • 字符串基础:Hash,KMP,trie
    Hash把一个字符串映射成一个整数,可以方便的比较两个字符串是否相等,计算\(Hash\)值:\[\displaystyle\sum_{i=0}^{len-1}(s[i]\timesB^{len-1-i})(mod\;M)\]这里的\(B\)是任取的一个大小合适的数,\(M\)就是为了把算出来的值映射到\([0,M-1]\)的范围内,既然是\(mod\),......
  • 151. 反转字符串中的单词
    题目链接:151.反转字符串中的单词这题主要是熟悉java一些库的调用,先放代码:classSolution{publicStringreverseWords(Strings){s=s.trim();//去除两边多余空格List<String>list=Arrays.asList(s.split("\\s+"));//将字符串按空格切割Coll......
  • Python字符串过滤器:正则表达式Regular Expression
    一、什么是正则表达式正则表达式是按照正确的既定规则、一种全语言类型Python、Java、JavaScript、PHP通用的表达式。用途:(1)根据规则抓取数据:配合爬虫、根据规则在文本中提取数据(2)根据规则验证数据:验证手机号、验证邮箱、验证身份证二、如何在Python中使用正则表达式在Python......
  • Python中列表和字符串的反转
    一、Python现成的反转功能:在Python中有专门进行列表反转的函数--reverse()l=[13,30,42,85,9,45]l.reverse()#[45,9,85,42,30,13]还可以使用切片操作进行列表反转l=[13,30,42,85,9,45]print(l[::-1])#[45,9,85,42,30,13]关于字符串的反转,并没......
  • Python 字符串格式化指南
    前言在Python中,字符串格式化是一种常见且重要的操作,用于将变量或值插入到字符串中,并控制输出的格式。本文将介绍几种常见的字符串格式化方法,帮助大家掌握在Python中有效地处理字符串的技巧。方法一:使用%操作符格式化字符串使用%操作符是一种传统的字符串格式化方法,可......
  • python 基础习题2--字符串切片技术
    1. 有如下字符串str='123456789'字符串切片技术,例如,返回输出从第三个开始到第六个的字符(不包含)即得到:345利用字符串切片技术,代码可以这么写:print(str[2:5])如果想返回如下八行结果,利用字符串切片技术,如何编写代码?12345678912345678134534567892412345678912345678......
  • mybatisplus分页中,模糊匹配一个字符串在列a或者列b下都可以筛选出的写法
    话不多说,直接上代码,and那句就对了LambdaQueryWrapper<类>wrapper=newLambdaQueryWrapper<类>().in(逻辑内容).like(正常逻辑内容).and(wrapperNew->wrapperNew.like(StringUtils.isNotEmpty(filter.getLocation()),......
  • excel判断字符串包含另一个字符串
    在Excel中,判断一个字符串是否包含另一个字符串,可以使用多种方法。以下是一些常用的方法:使用FIND函数。此函数会返回找到的字符串的首个字符的位置,如果返回#VALUE错误,则说明不包含目标字符串。1使用SUBSTITUTE函数。通过替换源字符串中的每个字符,然后与目标字符串比较,如果SUBSTI......