朝花夕拾

Day1

学字符串哈希,逆天的是,我现在才知道字符串哈希的小下标是高位,大下标是低位,我一直都是反过来的,难怪感觉这么奇怪不用求逆元,实际上字符串哈希就是一个 \(\mathrm{base}\) 进制的数,不需要反转过来,然后知道了二分+哈希可以做 \(\mathrm{lcp}\),我也忘了还有什么可以求 \(\mathrm{lcp}\) 了。

例题:月光

Statement

给定两个字符串 \(s,t\),你需要将 \(t\) 划分成若干段,然后要求每一段都是 \(s\) 的后缀,求最小划分段数,不合法输出 \(-1\)。时间复杂度 \(O(n\log(n))\)

Solution

首先很容易设计出划分数 \(\mathrm{dp}\)\(f_i=f_j+1,t[j+1,i]=s[1,i-j]\)。第一眼看上去像等差数列,确实可以这么做,但是还有一个比较巧的办法。我们稍微改变一下形式,让 \(j\) 贡献到 \(i\),这样的好处就是 \(j\) 能贡献到的位置是一段区间,容易发现区间的长度是 \(t[j,m]\)\(s\)\(\mathrm{lcp}\),然后感觉就像单点查然后区间取 \(\min\),要用 \(\mathrm{segbeat}\),仔细想了一下,实际上可以改成前缀取 \(\min\),因为前面的值我们不会在后面查询,这样就可以用类似差分的思想直接把修改挂在区间右端点上,直接树状数组查后缀 \(\min\) 就做完了。

Code

#include<bits/stdc++.h>
using ull=unsigned long long;
using namespace std;
namespace IO{
	#define gc getchar
	#define pc putchar
	#define ps puts
	inline int in(){
		int x=0,f=1;
		char c=gc();
		for(;!isdigit(c);c=gc())if(c=='-')f=-1;
		for(;isdigit(c);c=gc())x=x*10+(c-'0');
		return x*f;
	}
	inline void out(int x){
		if(x<0)pc('-'),x=-x;
		if(x>=10)out(x/10);
		pc(x%10+'0');
	}
	inline void outsp(int x){out(x),pc(' ');}
	inline void outln(int x){out(x),ps("");}
}
using namespace IO;
const int N=5e5+5,B=131;
int n,m,f[N];
ull h[2][N],pw[N];
char s[N],t[N];
ull calc(bool p,int l,int r){return h[p][r]-h[p][l-1]*pw[r-l+1];}
void add(int x,int y){for(;x>=1;x-=x&-x)f[x]=min(f[x],y);}
int ask(int x){if(!x)return 0;int y=N;for(;x<=m;x+=x&-x)y=min(y,f[x]);return y;}
int main(){
	scanf("%s%s",s+1,t+1),n=strlen(s+1),m=strlen(t+1);
	pw[0]=1;
	for(int i=1;i<=max(n,m);i++)pw[i]=pw[i-1]*B;
	for(int i=1;i<=n;i++)h[0][i]=h[0][i-1]*B+(s[i]-'a');
	for(int i=1;i<=m;i++)h[1][i]=h[1][i-1]*B+(t[i]-'a');
	for(int i=1;i<=m;i++)f[i]=N;
	for(int i=0;i<m;i++){
		int x=ask(i);
		if(x>=N)continue;
		int L=0,R=min(n,m-i);
		while(L<R){
			int M=(L+R+1)>>1;
			if(calc(1,i+1,i+M)==calc(0,1,M))L=M;
			else R=M-1;
		}
		if(L)add(i+L,x+1);
	}
	int x=ask(m);
	out(x<N?x:-1);
	return 0;
}

Day2

什么叫点双里面可以有割点啊,原来根本不知道,然后把 \(\mathrm{tarjan}\) 的模板题全部过了一边。发现有些例题我都不会啊啊啊。

例题:矿场搭建

Statement

给你一个无向图,你要安排最少的安全出口并输出方案数,保证这个图无论删掉哪一个点,全图的点都能到达安全出口。

Solution

首先考虑求出割点和点双。然后我们在每个点双之间分析。注意到,一个点双的割点数量等于这个点双到外部的通路数量,所以说我们可以分类讨论出点双里面的割点数。如果没有割点,那么说明和外界没有连边,那么这个点双里面至少要有两个安全出口,不然炸掉其中一个点双里面就没有出口了。如果有一个割点,那么这个点双内要有一个非割点是安全出口,因为如果割点没炸,可以通过割点去其他点双的出口,如果炸了,只能在点双里面找出口了。如果有至少两个割点,那么无论炸掉哪个割点,我都可以到达外界,所以不需要建安全出口。方案数也是好求的。

Code

#include<bits/stdc++.h>
#define int long long
#define pb push_back
using namespace std;
namespace IO{
	#define gc getchar
	#define pc putchar
	#define ps puts
	inline int in(){
		int x=0,f=1;
		char c=gc();
		for(;!isdigit(c);c=gc())if(c=='-')f=-1;
		for(;isdigit(c);c=gc())x=x*10+(c-'0');
		return x*f;
	}
	inline void out(int x){
		if(x<0)pc('-'),x=-x;
		if(x>=10)out(x/10);
		pc(x%10+'0');
	}
	inline void outsp(int x){out(x),pc(' ');}
	inline void outln(int x){out(x),ps("");}
}
using namespace IO;
const int N=505;
int n,m,k,tot,dfn[N],low[N];
bool cut[N];
stack<int> s;
vector<int> g[N],h[N];
void dfs(int u,int f){
	dfn[u]=low[u]=++tot,s.push(u);
	int c=0;
	for(int v:g[u])if(v!=f){
		if(!dfn[v]){
			dfs(v,u),c++,low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u]){
				if(f)cut[u]=1;
				k++;
				while(s.top()!=v)h[k].pb(s.top()),s.pop();
				h[k].pb(v),s.pop(),h[k].pb(u);
			}
		}
		else low[u]=min(low[u],dfn[v]);
	}
	if(!f&&c>1)cut[u]=1;
	if(!f&&!c)h[++k].pb(u);
}
signed main(){
	for(int t=1;;t++){
		m=in();
		if(!m)break;
		for(int i=1;i<=m;i++){
			int u=in(),v=in();
			g[u].pb(v),g[v].pb(u);
			n=max({n,u,v});
		}
		dfs(1,0);
		int ans1=0,ans2=1;
		for(int i=1;i<=k;i++){
			int cnt=0,c=h[i].size();
			for(int u:h[i])cnt+=cut[u];
			if(cnt==0)ans1+=2,ans2*=c*(c-1)/2;
			if(cnt==1)ans1++,ans2*=c-1;
			h[i].clear();
		}
		printf("Case %lld: %lld %lld\n",t,ans1,ans2);
		while(!s.empty())s.pop();
		for(int i=1;i<=n;i++)dfn[i]=low[i]=cut[i]=0,g[i].clear();
		n=k=tot=0;
	}
	return 0;
}

Day3

学树剖,怎么代码这么长啊服了,板子题调了好久,最后还是汪总发现函数传参反了,才知道,我又犯了经常犯的错:输入直接传参,然后写树剖板子的时候发现树剖 \(\mathrm{LCA}\) 也可以用 \(\mathrm{dfn}\) 维护,不用记录深度。

posted @ 2026-03-11 12:30  Lqs314  阅读(1)  评论(0)    收藏  举报