【做题记录】HZOJ 多校-数论/多校-字符串/多校-图论Ⅱ

14. 图论 E. Minimum Path

考虑这个路径权值的实质,就是将最大值不算,最小值算两次。我们直接将其拓展,改成任选一条边不算,一条边算两次,由贪心可知显然不会改变答案。于是将每个点拆成四个点,跑 dijkstra 即可。

Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
using namespace std;
namespace asbt{
const int maxn=2e5+5;
int n,m,dis[maxn][4];
bool vis[maxn][4];
vector<pii> e[maxn];
priority_queue<pair<int,pii>> q;
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	for(int i=1,u,v,w;i<=m;i++){
		cin>>u>>v>>w;
		e[u].pb(mp(v,w));
		e[v].pb(mp(u,w));
	}
	memset(dis,0x3f,sizeof(dis));
	dis[1][0]=0,q.push(mp(0,mp(1,0)));
	while(q.size()){
		int u=q.top().sec.fir,s=q.top().sec.sec;
		q.pop();
		if(vis[u][s]){
			continue;
		}
		vis[u][s]=1;
		for(pii i:e[u]){
			int v=i.fir,w=i.sec;
			if(!vis[v][s]&&dis[v][s]>dis[u][s]+w){
				dis[v][s]=dis[u][s]+w;
				q.push(mp(-dis[v][s],mp(v,s)));
			}
			if(!(s&1)&&!vis[v][s|1]&&dis[v][s|1]>dis[u][s]){
				dis[v][s|1]=dis[u][s];
				q.push(mp(-dis[v][s|1],mp(v,s|1)));
			}
			if(!(s&2)&&!vis[v][s|2]&&dis[v][s|2]>dis[u][s]+2*w){
				dis[v][s|2]=dis[u][s]+2*w;
				q.push(mp(-dis[v][s|2],mp(v,s|2)));
			}
			if(!s&&!vis[v][3]&&dis[v][3]>dis[u][s]+w){
				dis[v][3]=dis[u][s]+w;
				q.push(mp(-dis[v][3],mp(v,3)));
			}
		}
	}
	for(int i=2;i<=n;i++){
		cout<<dis[i][3]<<' ';
	}
	return 0;
}
}
signed main(){return asbt::main();}

15. 字符串 C. Prefix Function Queries

每次直接暴力求前缀数组是不可行的,因为 kmp 求前缀数组的时间复杂度是均摊 \(O(1)\)。考虑求前缀数组 \(fail_i\) 的过程,如果类似 ACAM 建一个 \(fail\) 树,我们实际上是从 \(i-1\) 开始在树上找到第一个 \(s_{j+1}=s_i\) 的位置。于是我们考虑建一个自动机(kmp 自动机),维护 \(nxt_{i,k}\) 表示 \(i\)\(fail\) 树的根链上的第一个 \(s_{k+1}=s_{i+1}\) 的位置 \(k\),于是有 \(fail_i=nxt_{fail_{i-1},s_i}+1\)

考虑 \(nxt\) 怎么维护,类似 ACAM 的思路,有 \(nxt_{i,j}=\begin{cases}\begin{aligned}&i&j=s_{i+1}\\&nxt_{fail_i,j}&j\ne s_{i+1}\end{aligned}\end{cases}\)。于是时间复杂度 \(O(26(|s|+\sum|t|))\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e6+39;
int n,m,fail[maxn],nxt[maxn][26];
char s[maxn];
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>(s+1)>>m;
	n=strlen(s+1);
	for(int i=2,j=0;i<=n;i++){
		while(j&&s[j+1]!=s[i]){
			j=fail[j];
		}
		if(s[j+1]==s[i]){
			j++;
		}
		fail[i]=j;
	}
	for(int i=0;i<n;i++){
		for(int j=0;j<=25;j++){
			nxt[i][j]=nxt[fail[i]][j];
		}
		nxt[i][s[i+1]-'a']=i;
	}
	while(m--){
		cin>>(s+n+1);
		int k=strlen(s+n+1);
		for(int i=n,j=fail[n];i<n+k;i++){
			for(int j=0;j<=25;j++){
				nxt[i][j]=nxt[fail[i]][j];
			}
			nxt[i][s[i+1]-'a']=i;
			j=nxt[j][s[i+1]-'a'];
			if(s[j+1]==s[i+1]){
				j++;
			}
			fail[i+1]=j;
			cout<<j<<' ';
		}
		cout<<'\n';
	}
	return 0;
}
}
signed main(){return asbt::main();}

17. 字符串 F. 【模板】AC 自动机

什么玩意儿也能进题单了。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=2e5+5;
int n,tot,tr[maxn][26],fail[maxn],q[maxn],end[maxn],ans[maxn],deg[maxn];
string s;
il void build(){
	int hd=1,tl=0;
	for(int i=0;i<=25;i++){
		if(tr[0][i]){
			q[++tl]=tr[0][i],deg[0]++;
		}
	}
	while(hd<=tl){
		int u=q[hd++];
		for(int i=0;i<=25;i++){
			if(tr[u][i]){
				fail[tr[u][i]]=tr[fail[u]][i];
				deg[tr[fail[u]][i]]++;
				q[++tl]=tr[u][i];
			}else{
				tr[u][i]=tr[fail[u]][i];
			}
		}
	}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>s;
		int p=0;
		for(char i:s){
			int d=i-'a';
			if(!tr[p][d]){
				tr[p][d]=++tot;
			}
			p=tr[p][d];
		}
		end[i]=p;
	}
	build();
	cin>>s;
	int p=0;
	for(char i:s){
		p=tr[p][i-'a'];
		ans[p]++;
	}
	int hd=1,tl=0;
	for(int i=0;i<=tot;i++){
		if(!deg[i]){
			q[++tl]=i;
		}
	}
	while(hd<=tl){
		int u=q[hd++];
		ans[fail[u]]+=ans[u];
		if(--deg[fail[u]]==0){
			q[++tl]=fail[u];
		}
	}
	for(int i=1;i<=n;i++){
		cout<<ans[end[i]]<<'\n';
	}
	return 0;
}
}
signed main(){return asbt::main();}

18. 图论 F. [GXOI/GZOI2019] 旅行者

直接二进制分组即可,时间复杂度线性对数方。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
using namespace std;
namespace asbt{
const int maxn=1e5+5;
const ll inf=1e18;
int T,n,m,kk,a[maxn];
ll dis[maxn];
bool vis[maxn];
vector<pii> e[maxn];
priority_queue<pii> q;
il void dijkstra(){
	while(q.size()){
		int u=q.top().sec;
		q.pop();
		if(vis[u]){
			continue;
		}
		vis[u]=1;
		for(pii i:e[u]){
			int v=i.fir,w=i.sec;
			if(!vis[v]&&dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				q.push(mp(-dis[v],v));
			}
		}
	}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		cin>>n>>m>>kk;
		for(int i=1,u,v,w;i<=m;i++){
			cin>>u>>v>>w;
			e[u].pb(mp(v,w));
		}
		for(int i=1;i<=kk;i++){
			cin>>a[i];
		}
		ll ans=inf;
		for(int i=0;i<=16;i++){
			memset(dis,0x3f,sizeof(dis));
			memset(vis,0,sizeof(vis));
			for(int j=1;j<=kk;j++){
				if(j>>i&1){
					dis[a[j]]=0,q.push(mp(0,a[j]));
				}
			}
			dijkstra();
			for(int j=1;j<=kk;j++){
				if(!(j>>i&1)){
					ans=min(ans,dis[a[j]]);
				}
			}
			memset(dis,0x3f,sizeof(dis));
			memset(vis,0,sizeof(vis));
			for(int j=1;j<=kk;j++){
				if(!(j>>i&1)){
					dis[a[j]]=0,q.push(mp(0,a[j]));
				}
			}
			dijkstra();
			for(int j=1;j<=kk;j++){
				if(j>>i&1){
					ans=min(ans,dis[a[j]]);
				}
			}
		}
		cout<<ans<<'\n';
		for(int i=1;i<=n;i++){
			e[i].clear();
		}
	}
	return 0;
}
}
signed main(){return asbt::main();}

19. 数论 U. [abc322_g]Two Kinds of Base

\(F(S,a,b)=f(S,a)-f(S,b)=\sum_{i=1}^{k}S_i\times(a^{k-i}-b^{k-i})\),因为 \((a-b)|(a^k-b^k)\),有 \((a-b)|F(S,a,b)\)。由于 \(F(S,a,b)\) 关于 \(k\) 是指数级的,因此 \(k\) 非常小,不会超过 \(18\)

注意到当 \(k\ge3\) 时,有限制 \(a^2-b^2\le X\),此时合法的 \((a,b)\) 也非常少,大概是 \(O(X\log X)\) 级别。

Proof:令 \(s=a-b\),则 \(s|F(S,a,b)\)。由 \(a^2-b^2\le X\)\(s^2+2bs\le X\),故对于每个 \(s\)\(b\) 最多有 \(\frac{X}{s}\) 个,这是一个调和级数。

于是我们可以枚举 \(k\)\(a-b\),然后再枚举出合法的 \((a,b)\),对 \(S\) 进行计数。实际上由于 \(a^k-b^k>(b-1)\sum_{i=0}^{k-1}a^i-b^i\)(可以归纳证明),\(S\) 如果存在则前 \(k-1\) 位是唯一确定的,从大到小贪心地减去 \(a^{k-i}-b^{k-i}\) 即可,时间复杂度 \(O(k)\),也就是 \(O(\log X)\)。因此这一部分的总时间复杂度大概是线性对数方级别。

但是还有一种情况,即 \(k=2\)。此时我们发现,\(F(S,a,b)=S_1\times(a-b)\)。不妨枚举 \(S_1\),则 \(a-b=\frac{X}{S_1}\),故 \(b\in(S_1,N-\frac{X}{i}]\),对于 \(<10\)\(b\) 单独计算 \(S_2\) 的方案数,对于 \(\ge10\)\(b\) \(S_2\)\(10\) 种填法,\(O(1)\) 计算即可。

总时间复杂度 \(O(X\log^2X)\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int mod=998244353;
il int pls(int x,int y){
	return x+y<mod?x+y:x+y-mod;
}
il void add(int &x,int y){
	x=pls(x,y);
}
il int mns(int x,int y){
	return x<y?x-y+mod:x-y;
}
il void sub(int &x,int y){
	x=mns(x,y);
}
int n,m;
il bool check(int a,int b,int k){
	int x=m;
	for(int i=k-1;i;i--){
		int t=pow(a,i)-pow(b,i);
		if(x/t>min(9,b-1)){
			return 0;
		}
		x%=t;
	}
	return !x;
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n>>m;
	int ans=0;
	for(int i=1;i<=9;i++){
		if(m%i){
			continue;
		}
		for(int j=i+1;j<=9;j++){
			if(j>n-m/i){
				break;
			}
			add(ans,j);
		}
		ans=(ans+max(0,n-m/i-9)*10ll)%mod;
	}
	for(int i=1;i<=m;i++){
//		cout<<"i = "<<i<<'\n';
		if(m%i){
			continue;
		}
		for(int k=3;k<=18;k++){
//			cout<<"  k = "<<k<<'\n';
			for(int a=i+2,b=2;a<=n&&pow(a,k-1)-pow(b,k-1)<=m;a++,b++){
//				if(i==16&&k==17){
//					cout<<a<<' '<<b<<' ';
//				}
				if(check(a,b,k)){
					add(ans,min(b,10));
				}
//				if(i==16&&k==17){
//					puts("666");
//				}
			}
		}
	}
	cout<<ans;
	return 0;
}
}
signed main(){return asbt::main();}

20. 数论 R. [NOI Online 2022 入门组] 数学游戏

首先如果 \(x\nmid z\) 则必然无解。

\(\gcd(x,y)=d,x=ad,y=bd\),则 \(z=abd^3\)。于是我们可以求出 \(bd^2=\frac{z}{x}\)。又 \(a\perp b\),可以得到 \(d^2=\gcd(x^2,\frac{z}{x})\)。于是 \(y=\frac{z}{x\sqrt{\gcd(x^2,\frac{z}{x})}}\)

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
int T;
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>T;
	while(T--){
		ll x,z;
		cin>>x>>z;
		if(z%x){
			cout<<-1<<'\n';
			continue;
		}
		ll d2=__gcd(z/x,x*x);
		ll d=sqrtl(d2);
		if(d*d==d2){
			cout<<z/x/d<<'\n';
		}else{
			cout<<-1<<'\n';
		}
	}
	return 0;
}
}
signed main(){return asbt::main();}

21. 数论 I. [THUPC2019] 令人难以忘记的题目名称

过于复杂,另写了个题解

22. 图论 L. Blood Cousins Return

离线,用线段树维护子树内每个深度有哪些儿子(给线段树叶子节点开个 set),线段树合并(叶子节点启发式合并)即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int n,m,a[maxn],fa[maxn],ans[maxn];
int rt[maxn],tot,ls[maxn*19],rs[maxn*19];
string s;
vector<int> e[maxn];
vector<pii> q[maxn];
set<int> st[maxn*19];
map<string,int> id;
il void insert(int &id,int l,int r,int p,int x){
	if(!id){
		id=++tot;
	}
	if(l==r){
		st[id].insert(x);
		return ;
	}
	int mid=(l+r)>>1;
	if(p<=mid){
		insert(ls[id],l,mid,p,x);
	}else{
		insert(rs[id],mid+1,r,p,x);
	}
}
il int merge(int p,int q,int l,int r){
	if(!p||!q){
		return p+q;
	}
	if(l==r){
		if(st[p].size()<st[q].size()){
			swap(st[p],st[q]);
		}
		for(int x:st[q]){
			st[p].insert(x);
		}
		return p;
	}
	int mid=(l+r)>>1;
	ls[p]=merge(ls[p],ls[q],l,mid);
	rs[p]=merge(rs[p],rs[q],mid+1,r);
	return p;
}
il int query(int id,int l,int r,int p){
	if(!id){
		return 0;
	}
	if(l==r){
//		for(int x:st[id]){
//			cout<<x<<' ';
//		}
//		cout<<'\n';
		return st[id].size();
	}
	int mid=(l+r)>>1;
	if(p<=mid){
		return query(ls[id],l,mid,p);
	}else{
		return query(rs[id],mid+1,r,p);
	}
}
il void dfs(int u,int d){
	for(int v:e[u]){
		dfs(v,d+1);
		rt[u]=merge(rt[u],rt[v],1,n);
	}
	insert(rt[u],1,n,d,a[u]);
	for(pii i:q[u]){
//		cout<<u<<' '<<i.fir<<":\n";
		if(d+i.fir<=n){
			ans[i.sec]=query(rt[u],1,n,d+i.fir);
		}
	}
}
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>n;
	for(int i=1,x,cnt=0;i<=n;i++){
		cin>>s>>x;
		if(!id.count(s)){
			id[s]=++cnt;
		}
		a[i]=id[s],fa[i]=x;
		if(x){
			e[x].pb(i);
		}
//		cout<<a[i]<<' ';
	}
//	cout<<'\n';
	cin>>m;
	for(int i=1,v,k;i<=m;i++){
		cin>>v>>k;
		q[v].pb(mp(k,i));
	}
	for(int i=1;i<=n;i++){
		if(!fa[i]){
			dfs(i,1);
		}
	}
	for(int i=1;i<=m;i++){
		cout<<ans[i]<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}

23. 字符串 B. Prefixes and Suffixes

跑出 \(nxt\) 数组,在 \(nxt\) 树上做个前缀和即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int n,nxt[maxn],cnt[maxn];
string s;
vector<pii> ans;
int main(){
	ios::sync_with_stdio(0),cin.tie(0);
	cin>>s;
	n=s.size(),s=" "+s;
	for(int i=2,j=0;i<=n;i++){
		while(j&&s[j+1]!=s[i]){
			j=nxt[j];
		}
		if(s[j+1]==s[i]){
			j++;
		}
		nxt[i]=j;
	}
	for(int i=n;i;i--){
		cnt[i]++,cnt[nxt[i]]+=cnt[i];
	}
	for(int i=n;i;i=nxt[i]){
		ans.pb(mp(i,cnt[i]));
	}
	cout<<ans.size()<<'\n';
	sort(ans.begin(),ans.end());
	for(pii i:ans){
		cout<<i.fir<<' '<<i.sec<<'\n';
	}
	return 0;
}
}
int main(){return asbt::main();}
posted @ 2025-11-21 07:37  zhangxy__hp  阅读(22)  评论(0)    收藏  举报