2021-09-11 训练场补题

该博客介绍了如何使用AC自动机优化算法解决字符串匹配问题,通过在匹配过程中标记长度,避免暴力跳fail指针,仅在必要时回溯,适用于大规模模式串和匹配串。涉及的结构体数组优化和构建fail树的思想有助于提高效率。
摘要由CSDN通过智能技术生成

C - Family View

题意:
给你 n n n个模式串,模式串中只有小写字母,模式串长度之和 l e n < = 1 e 6 len<=1e6 len<=1e6,然后给你一个匹配串 l e n < = 1 e 6 len<=1e6 len<=1e6,匹配串由全字符集组成。要求将匹配串中的所有和模式串匹配的子段替换成 ‘*’ ;
思路:
优化AC自动机;普通的AC自动机在匹配的时候会暴力跳fail指针;也有建fail 树来统计每个匹配串在文本串中出现的次数的优化。但是这道题我们只用在匹配的地方加个标记,记录长度即可,想象原本跳fail的过程,对于自动机上每个fail节点,我们要对他的flag取max,然后加标记,那么我们在建fail边的时候,直接取max就好了,这样就不用跳fail边了。那么我们只要遍历自动机,如果当前文本串的字符不是字母,那么我们直接回到自动机的元节点即可。
结构体数组不要赋初值!默认会把全数组赋值,然后就MLE了。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=7e5+4;

int t,n;
struct node{
	int son[26],fail=0,flag;
}trie[maxn];

int p[maxn];
int tot=1;
queue<int>q;
char s[maxn];
void init(){
	tot=0;
	for(int j=0;j<26;j++){
		trie[tot].son[j]=0;
	}
	trie[tot].fail=0;
	trie[tot].flag=0;
}
void insert(char *s){
	int len=strlen(s),u=0;
	for(int i=0;i<len;i++){
		int v=s[i]-'a';
		if(!trie[u].son[v]){
			trie[u].son[v]=++tot;
			for(int j=0;j<26;j++){
				trie[tot].son[j]=0;
			}
			trie[tot].fail=0;
			trie[tot].flag=0;
		}
		u=trie[u].son[v];
	}
	trie[u].flag=max(trie[u].flag,len);
}
void getfail(){
	for(int i=0;i<26;i++)
		if(trie[0].son[i])q.push(trie[0].son[i]);
	while(!q.empty()){
		int u=q.front();q.pop();
		trie[u].flag=max(trie[u].flag,trie[trie[u].fail].flag);
		for(int i=0;i<26;i++){
			int v=trie[u].son[i];
			int x=trie[u].fail;
			if(!v){
				trie[u].son[i]=trie[x].son[i];
			}else{
				trie[v].fail=trie[x].son[i];
				q.push(v);
			}
		}
	}
}
void query(char *s){
	int u=0,len=strlen(s);
	for(int i=0;i<len;i++)p[i]=0;
	for(int i=0;i<len;i++){
		int v=-1;
		if(s[i]>='A'&&s[i]<='Z')v=s[i]-'A';
		if(s[i]>='a'&&s[i]<='z')v=s[i]-'a';
		if(v==-1)u=0;
		else u=trie[u].son[v];
		if(trie[u].flag){
			p[i+1]--;
			p[i-trie[u].flag+1]++;
		}
	}
	int res=0;
	for(int i=0;i<len;i++){
		res+=p[i];
		if(res==0)putchar(s[i]);
		else putchar('*');
	}
	printf("\n");
}
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		init();
		for(int i=1;i<=n;i++){
			scanf("%s",s);
			insert(s);
		}
		getfail();
		getchar();
		scanf("%[^\n]",s);
		query(s);
	}
	return 0;
}
//D I K L 

D - Tea

题意:
懒得写

思路:
分类讨论
看代码应该看得懂

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e6+4;
int t,n;
ll l,r;
ll ans=0;
int main(){
	while(cin>>l>>r){
		ans=1e18;
		ll res=r-l;
		if(res<=3)ans=2;
		else ans=(res-3+1)/2+2;
		if(r<=2)ans=1;
		if(r<=1)ans=0;
		if(l==0)ans=min(ans,(r+1)/2);
		cout<<ans<<endl;
	}
	return 0;
}
//D I K L 

I - Tower Defence

题意:
给你 n n n个节点的带边权的树( n < = 1 e 5 n<=1e5 n<=1e5),定义每次删除一个边,对答案的贡献是分开的两颗子树中直径的较大值。求分别删除每条边对答案的贡献和。
思路:
我们分成两种情况来考虑:
如果删除的边不是整棵树的直径上的边,那么对答案的贡献就还是整棵树的直径。
如果删除的边不是整棵树的直径,我们对两边子树的直径取较大值。
我们可以比较简单地证明出,删掉一条边之后,子树的直径一定是由原直径的端点出发的。(如果不是从端点出发的最长链,那么他就会取代现在端点的这条链,加入原直径)。
那么我们就可以预处理出删除直径上的一条边后,子树的最大直径,即对于每个点,求他不经过直径最远能延伸多少,然后左右分别做一次DP, d p [ i ] = m a x ( l e n , n o w + c n t ) dp[i]=max(len,now+cnt) dp[i]=max(len,now+cnt),最后再跑一次直径取较大值加到答案中即可。
代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long

ll n,t;
struct node{
	ll to,w;
};
vector<node>v[100040];
ll dep[100040];
bool vis[100040];
int root,root2;
ll sum=0;
ll ans=0;
ll res=0;
ll dp1[100040];
ll dp2[100040];
void dfs1(int x,int pre){
	if(dep[x]>dep[root])root=x;
	for(int i=0;i<v[x].size();i++){
		if(v[x][i].to==pre)continue;
		dep[v[x][i].to]=dep[x]+v[x][i].w;
		dfs1(v[x][i].to,x);
	}
}
void dfs2(int x){
	vis[x]=1;res++;
	for(int i=0;i<v[x].size();i++){
		if(dep[x]==dep[v[x][i].to]+v[x][i].w){
			dfs2(v[x][i].to);
		}
	}
}
ll cnt=0;
ll now=0;
ll lenn=0; 
void dfs4(int x,int pre,ll d){
	cnt=max(cnt,d);
	for(int i=0;i<v[x].size();i++){
		if(v[x][i].to==pre||vis[v[x][i].to])continue;
		dfs4(v[x][i].to,x,d+v[x][i].w);
	}
}
int f=0;
void dfs3(int x,int pre){
	cnt=0;
	if(f==0&&x!=root2){
		dfs4(x,0,0);
		dp1[x]=max(lenn,now+cnt);
		lenn=max(lenn,now+cnt);
	}
	if(f==1&&x!=root){
		dfs4(x,0,0);
		dp2[x]=max(lenn,now+cnt);
		lenn=max(lenn,now+cnt);
	}
	for(int i=0;i<v[x].size();i++){
		if(v[x][i].to==pre||vis[v[x][i].to]==0)continue;
		now+=v[x][i].w;
		dfs3(v[x][i].to,x);
	}
}
void dfs5(int x,int pre){
	if(x!=root2){
		ans+=max(dp1[pre],dp2[x]);
	}
	for(int i=0;i<v[x].size();i++){
		if(v[x][i].to==pre||vis[v[x][i].to]==0)continue;
		dfs5(v[x][i].to,x);
	}
}
int main(){
	scanf("%lld",&t);
	while(t--){
		scanf("%lld",&n);
		for(int i=1;i<n;i++){
			ll a,b,c;
			scanf("%lld%lld%lld",&a,&b,&c);
			v[a].push_back({b,c});
			v[b].push_back({a,c});
		}
		for(int i=1;i<=n;i++)vis[i]=0,dp1[i]=0,dp2[i]=0;
		root=0;
		dep[0]=0;
		dep[1]=0;
		dfs1(1,0);
		root2=root;
		dep[root]=0;
		dfs1(root,0);
		sum=dep[root];
		ans=0;res=0;
		dfs2(root);
		ans=1LL*sum*(n-res);
		now=0;lenn=0;
		f=0;
		dfs3(root2,0);
		f=1;
		now=0;lenn=0;
		dfs3(root,0);
		dfs5(root2,0);
		printf("%lld\n",ans);
		for(int i=1;i<=n;i++)v[i].clear();
	}
	return 0;
}
//K L 

K - Barricade

题意:
给你 n n n个点 m m m条边的图,你需要堵住从1到 n n n的所有最短路,堵住每条边会有一个代价。求堵住最短路的最小代价。
思路:
比赛当时,想了一个有的没的DP转移,后来发现不对。
然后其实就是一个网络流裸题,作为网络流选手,没看出来是不应该的,需要自己反思。
就先把最短路扣出来,堵住这些路就是把点1和点 n n n,分成两部分,典型的最小割。
代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long

int t,n,m;
struct node{
	int to,w;
};
vector<node>v[1004];
int dis1[1004],dis2[1004];
queue<int>q;
struct edge{
	int to,cap,rev;
}; 
vector<edge>mp[1004];
int level[1004];//顶点到源点的距离标号 
int iter[1004]; //当前弧,在其之前的边已经没有用了 
void add(int from,int to,int cap){
	mp[from].push_back({to,cap,mp[to].size()});
	mp[to].push_back({from,0,mp[from].size()-1});
}
void bfs(int s){
	memset(level,-1,sizeof(level));
	queue<int>q;
	level[s]=0;
	q.push(s);
	while(!q.empty()){
		int v=q.front();q.pop();
		for(int i=0;i<mp[v].size();i++){
			edge &e=mp[v][i];
			if(e.cap>0&&level[e.to]<0){
				level[e.to]=level[v]+1;
				q.push(e.to);
			}
		}
	}
	
}
int dfs(int v,int t,int f){
	if(v==t)return f;
	for(int &i=iter[v];i<mp[v].size();i++){
		edge &e=mp[v][i];
		if(e.cap>0&&level[v]<level[e.to]){
			int d=dfs(e.to,t,min(f,e.cap));
			if(d>0){
				e.cap-=d;
				mp[e.to][e.rev].cap+=d;
				return d;
			}
		}
	}
	return 0;
}
int max_flow(int s,int t){
	int flow=0;
	while(1){
		bfs(s);
		if(level[t]<0)return flow;
		memset(iter,0,sizeof(iter));
		int f;
		while((f=dfs(s,t,1e9))>0)flow+=f;
	}
    return flow;
}
int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&m);
		for(int i=1;i<=m;i++){
			int a,b,c;scanf("%d%d%d",&a,&b,&c);
			if(a==b)continue;
			v[a].push_back({b,c});
			v[b].push_back({a,c});			
		}
		for(int i=1;i<=n;i++){
			dis1[i]=dis2[i]=1e9;
		}
		dis1[1]=0;
		while(!q.empty())q.pop();
		q.push(1);
		while(!q.empty()){
			int x=q.front();q.pop();
			for(int i=0;i<v[x].size();i++){
				if(dis1[v[x][i].to]>dis1[x]+1){
					dis1[v[x][i].to]=dis1[x]+1;
					q.push(v[x][i].to);
				}
			}
		}
		dis2[n]=0;
		q.push(n);
		while(!q.empty()){
			int x=q.front();q.pop();
			for(int i=0;i<v[x].size();i++){
				if(dis2[v[x][i].to]>dis2[x]+1){
					dis2[v[x][i].to]=dis2[x]+1;
					q.push(v[x][i].to);
				}
			}
		}
		for(int i=1;i<=n;i++){
			for(int j=0;j<v[i].size();j++){
				if(dis1[i]+dis2[v[i][j].to]+1==dis1[n]){
					add(i,v[i][j].to,v[i][j].w);
				}
			}
		}
		printf("%d\n",max_flow(1,n));
		for(int i=1;i<=n;i++)v[i].clear(),mp[i].clear();
	} 
	return 0;
} 
//K L 

L - Eighty seven

题意:
给你 n n n个数( n < = 50 n<=50 n<=50), 每次保证不取小于等于3个数的前提下,问能否取十个数使得这十个数的和为87;

思路:
暴力背包+bitset优化

代码:

#include<bits/stdc++.h>
using namespace std;
bitset<90>s[11];
int a[51],f[51][51][51];
int n;
int solve(int x,int y,int z){
	for(int i=0;i<=10;i++)s[i].reset();
	s[0][0]=1;
	for(int i=1;i<=n;i++){
		if(i==x||i==y||i==z||a[i]>87)continue;
		for(int j=10;j>0;j--){
			s[j]|=s[j-1]<<a[i];
		}
	}
	return s[10][87];
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		for(int i=1;i<=n;i++)scanf("%d",&a[i]);
		for(int i=1;i<=n;i++){
			for(int j=i;j<=n;j++){
				for(int k=j;k<=n;k++){
					f[i][j][k]=solve(i,j,k);
				}
			}
		}
		int m;
		scanf("%d",&m);
		while(m--){
			int b[3];
			scanf("%d%d%d",&b[0],&b[1],&b[2]);
			sort(b,b+3);
			if(f[b[0]][b[1]][b[2]])printf("Yes\n");
			else printf("No\n");
		}
	}
}
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值