把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

来自 szc 的字符串和搜索的总结

膜拜 szc 大佬。

原链接

题单+代码

哈希

普通哈希不讲了,讲讲树哈希。

对于判断一对同构树,要考虑相同结构的儿子在两类树的不同位置。

此时有两种方法,一种是正常的按序哈希,我们很好想到在哈希时对儿子节点的哈希值进行排序,规定一个顺序塞进去。

另一种方法则是不使用多项式哈希,对所有哈希值在不乘上模数的情况下进行求和,这样可以保证其哈希值是顺序无关的,而且好回溯。

为了防卡,此时我们就需要改变一下哈希函数的式子,如变成异或哈希。

P5043 树同构

哈希表

总有题目用的到哈希表的。

这里讲一下对于一个哈希表的冲突,我们通常使用散列表的方式(应该是叫这个),也叫拉链法。

就是把哈希值取个模,对于取模后的哈希值当做起点,造出一个连向该哈希值的边,对于查询时遍历该哈希值的模数后判断是否存在就行了。

这里是单哈希的板子(未实战慎复制)

struct HashTable{
	struct edge{
		struct node {
			ull w;
			int nxt,to;
		    node(int x=0,ull y=0,int z=0){
		    	to=x,w=y,nxt=z;
			}
		}e[N];
		int head[mod+1],tot;
		inline void add(int u,int v,ull w){
			e[++tot]=node(v,w,head[u]),head[u]=tot;
		}
		inline node&operator[](int x){return e[x];}
	}e;
    inline int get(ull key) { return (key%mod+mod)%mod; }
	int query(ull a){
		for(int i=e.head[get(a)];i;i=e[i].nxt)
			if(e[i].w==a) return e[i].to;
		return -1;
	}
	inline void insert(ull a,int b){
		int tmp=get(a);
		e.add(tmp,a,b);
	}
}fyn;

P5537 【XR-3】系统设计

KMP

KMP 常见于字符串匹配,简单复习一下。

定义 \(f_i\) 表示以 \(i\) 为结尾的字符串的后缀与原串能匹配的最长前缀。

对于新增节点 \(i\),我们已经知道了 \(i-1\) 的串长这样:

1234....f[i-1],x,...,(i-1)-f[i-1]+1,...,i-1,i

其中可以保证 $ i-1\sim (i-1)-f[i-1]+1$ 这一串字符是与 \(1\sim f[i-1]\) 相匹配的,则接下来对于新加进来的 \(i\) ,我们要将其与 \(x\) 进行配对。

若是相同的则 \(f[i]=f[i-1]+1\)

若是不相同,我们就令失配指针跳到 \(f[i-1]\),即接下来串就变成了这个形式:

1234...f[f[i-1]],x,...f[i-1]-f[f[i-1]]+1,...,f[i-1],...,i

显然我们再对 \(x\) 进行以上的比较,直至失配指针变为 0 时,则代表 \(i\) 这个串找不到匹配的前缀。

kmp 例题就不放了,直接讲讲 kmp 套 dp 以及失配树。

P3426
P3502

以及 失配树

扩展KMP(Z函数)

考虑 \(z[i]\) 表示从 \(i\) 开始的字符串与原串的最长公共前缀。

考虑如何求得 \(z\)

首先朴素算法,我们从 \(1\sim n\) 枚举每个点 i ,再暴力向后扫直到无法添加前缀,时间复杂度是 \(O(n^2)\) 的。

显然我们能优化的只有暴力向后扫的这个循环,考虑如何将 \(z[i]\) 从上一位转移过来。

我们记 \(i-1\) 得到的右端点最右的匹配串为 \(l,r\) ,则根据定义可知 \(S[l\sim r]\)\(S\) 串的前缀。计算时保证 $l\le i $ 且初始 \(l=r=0\)

  • \(i\le r\),则我们可以知道此时的 \(i\) 是处于该匹配串中的,而 \(r+1\) 是无法匹配的,因此我们可以得出 \(z[i]=min(z[i-l+1],r-i+1)\)

  • 否则我们以 \(i\) 为左端点再往右跑朴素算法。

这样子最终也是 \(O(n)\) 的。

CF432D

Manacher

首先考虑最朴素的回文串算法,暴力枚举中心点然后向左右拓展到无法添加为止,复杂度 \(O(n^2)\)

首先回文串需要区分为奇数串和偶数串,而我们通过一个小技巧将其合并为一种情况。

我们首先往串中穿插 '#' ,构造出一个 \(2n+1\) 的字符串记为 \(S'\)

对于在 \(S'\) 中的回文串其两端必然是 #,而奇数回文串必然以原字符串中的字符为中心,偶数串则是以我们穿插进去的 '#' 为中心。

考虑上一次计算后最大 \(r\) 值得子回文串为 \(l,r\)

考虑新加入的 \(i\)

  • \(i>r\) 则说明当前中心在回文串之外,我们使用朴素算法暴力向外拓展并更新 \(l,r\)

  • \(i\le r\),则说明 \(i\) 处在我们当前的回文串中,其必然有一个对称点 \(j\) ,且可以保证以 \(i\) 为中心至少存在一个长度与以 \(j\) 为中心的回文串相同的回文串。

显然此时我们还需要考虑若以 \(j\) 为中心的回文串超出边界时,我们并不能保证超过 \(r\) 的部分是关于 \(i\) 对称的,于是进行阻断,然后再继续往外跑朴素算法。

此后记得更新 \((l,r)\)

CF30E

Trie

Trie,字典树,顾名思义,字典树,一对多或许可以试试。

P4407 [JSOI2009] 电子字典

当然也可以用来求异或和最大。

P8511 [Ynoi Easy Round 2021] TEST

A*,IDA*

首先,了解一下这两个东西。

不要以为这是一个很高深的东西,实际上它是一个不那么高深的东西。

事实上就是一个加了优化的暴力。

A* 实际上就是加了估价函数的 bfs,最经典的就是求 K 短路。

对于每次拓展都贪心地选择我们从开始到当前点的花费加上我们预估接下来的花费的和最小的那个点,并进行拓展。

P2901 [USACO08MAR] Cow Jogging G

IDA* 实际上就是加了估价函数和迭代加深 (没有tim<=1e6) 的dfs,一般适用于拥有限制 (可以不可以总司令) 的题目。

然而这里的选择就有些不同,对于一个状态我们需要预估出该状态在最优情况下到达最终态的步数,及能走的最小步数,然后判断其加上当前深度是否超出迭代的最大深度,若超出则返回。

其实就是剪枝。

P2324 [SCOI2005] 骑士精神

以及 P3179 八数码

贴个八数码的 IDA*

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	auto _u=[](char x){return x>='0'&&x<='9';};
	for(;!_u(ch);ch=='-'&&(f=-1),ch=getchar());
	for(;_u(ch);x=(x<<1)+(x<<3)+(ch^48),ch=getchar());
	return x*f;
}
inline void write(int num,bool flag=0){
	static int st[39],tp=0;
	num<0&&(putchar('-'),num=-num);
	do st[++tp]=num%10;while(num/=10);
	while(tp) putchar(st[tp--]|48);
	putchar(flag?'\n':' ');
	return;
}
template<typename...Args>
inline void write(int t,Args...args){
	return write(t),write(args...);
}
short mp[3][3]={
	{1,2,3},
	{8,0,4},
	{7,6,5}
};
short a[3][3];
inline int check(){
	int cnt=0;
	for(int i=0;i<3;++i)
		for(int j=0;j<3;++j)
			cnt+=(a[i][j]!=mp[i][j]);
	return cnt;
}
int d[4][2]={{0,1},{1,0},{-1,0},{0,-1}};
inline bool in(int x,int y){
	return x>=0&&x<3&&y>=0&&y<3;
}
bool dfs(int x,int y,int dep,int lst,int mxdep){
	if(dep==mxdep)return check()==0;
	for(int i=0,tx,ty;i<4;++i)
		if(in(tx=x+d[i][0],ty=y+d[i][1])&&lst+i!=3){
			swap(a[tx][ty],a[x][y]);
			if(check()+dep<=mxdep&&dfs(tx,ty,dep+1,i,mxdep)) return true;
			swap(a[tx][ty],a[x][y]);
		}
	return false;
}
inline void A_star(int x,int y){
	if(check()==0)puts("0");
	else for(int i=1;;++i) if(dfs(x,y,0,-1,i)) return write(i,true);
}
char s[10];
signed main(){
	scanf("%s",s);
	int x,y;
	for(int i=0;i<9;++i)
		if(!(a[i/3][i%3]=s[i]^48)) x=i/3,y=i%3;;
	A_star(x,y);
	return(0-0);
}

~

折半搜索,就是折一半搜索。

将原本的一组数据分组爆搜,再用一种时间复杂度较低的方法将各组数据合并为最终答案。

显然由此可得,我们不仅需要对可以暴力的数据范围敏感,还要对其倍数敏感。

P3067 [USACO12OPEN] Balanced Cow Subsets G

CF888E Maximum Subsequence

posted @ 2023-11-17 09:57  djh0314  阅读(28)  评论(0)    收藏  举报
浏览器标题切换
浏览器标题切换end