2022牛客多校联赛第三场 题解

比赛传送门
作者: fn


签到题

C题 Concatenation / 连接

题目大意
给定 n n n 个字符串,将他们拼接起来。
求结果的字典序最小的方案。 1 ≤ N ≤ 2 ∗ 1 0 6 1≤N≤2∗10^6 1N2106

考察内容
排序,复杂度优化

分析
解法不唯一。

一个 O ( n l o g n ) O(nlogn) O(nlogn) 的做法:直接sort排序。

可选的常数优化:

  1. 定义时限定string的大小。
  2. 使用快读。
#include<bits/stdc++.h>
using namespace std;
string s[int(2e6+5)];
bool cmp(string &a,string &b){
	return a+b<b+a;
}
int main(){
	int n;
	cin>>n;
	for(int i=0;i<n;i++)
		cin>>s[i];
	sort(s,s+n,cmp);
	for(int i=0;i<n;i++)
		cout<<s[i];
	return 0;
} 

基本题

A题 Ancestor / 祖先

题目大意
给出两棵结点编号 1 − n 1-n 1n 的树A、B,A、B树上每个节点均有一个权值,给出 k k k 个关键点的编号 𝑥 1 … 𝑥 𝑛 𝑥_1…𝑥_𝑛 x1xn
问有多少种方案,使得去掉恰好一个关键点使得剩余关键点在树A上LCA的权值大于树B上LCA的权值。

考察内容
lca,dfs序,模拟

分析
模板题。
已知多个结点的lca就是dfs序第一个结点和最后一个结点的lca。
预处理好dfs序,前两个结点,后两个结点,然后枚举删去的结点,累加答案即可。

#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e5+5;
ll n,m,k,a[N],pa[N],b[N],pb[N];
ll x[N];

vector<int> ga[N],gb[N];
int dfs_[200020],len;

void dfsa(int u,int fa){
    dfs_[++len]=u;  
    int sz=ga[u].size();
    for(int i=0;i<sz;i++){
        if(ga[u][i]!=fa){
            dfsa(ga[u][i],u);
        }
    }
}

void dfsb(int u,int fa){
    dfs_[++len]=u;  
    int sz=gb[u].size();
    for(int i=0;i<sz;i++){
        if(gb[u][i]!=fa){
            dfsb(gb[u][i],u);
        }
    }
}

ll xa1=-1,xa2=-1,xa3=-1,lastxa=-1;
ll xb1=-1,xb2=-1,xb3=-1,lastxb=-1;
map<int,bool> mp;

int h[N],e[N*2],ne[N*2],idx;
int f[N][21];
int d[N];
void add(int a,int b) // 存边
{ 
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}

void bfs(int u)  // 预处理
{
    queue<int> q; q.push(u),d[u] = 1;
    while(!q.empty()){
        int x = q.front(); q.pop();
        for(int i=h[x]; ~i; i = ne[i]){
            int y = e[i];
            if(d[y]) continue;          // 已经更新过的点 不需要在更新了
            d[y] = d[x]+1;
            f[y][0] = x; 
            for(int j = 1; j<=20; j++)  // 这里懒了 没有写log判断树的最高高度
                f[y][j] = f[f[y][j-1]][j-1];
            q.push(y);                  // 把当前点放入 队列中
        }
    }
}

int lca(int a,int b){ // 求a,b的lca 
    if(d[a]<d[b]) swap(a,b);
    for(int i=20; i>=0; i--)
        if(d[f[a][i]]>=d[b])  // 找到和b同高度的节点
            a = f[a][i];
    if(a==b) return a;
    for(int i=20; i>=0; i--)
        if(f[a][i]!=f[b][i]) a= f[a][i],b = f[b][i]; // 往上找有共同的根的位置
    
    return f[a][0];
}

ll ansa[N]; 
ll ansb[N];

int main(){
	memset(h, -1, sizeof h);  // 初始化头节点
	
	cin>>n>>k; // 每棵树n个结点,k个关键结点 
	for(int i=1;i<=k;i++){
		cin>>x[i]; // 给定k个关键结点
		mp[x[i]]=1; 
	}
	
	// 输入两棵树 
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}	
	for(int i=1;i<=n-1;i++){
		cin>>pa[i];
	}	
	for(int i=1;i<=n;i++){
		cin>>b[i];
	}	
	for(int i=1;i<=n-1;i++){
		cin>>pb[i];
	}
	
	
	len=0;
    for(int i=1;i<=n-1;i++){
        int from,to;
        from=i+1; to=pa[i];
        
        ga[from].push_back(to);
        ga[to].push_back(from);
        
        add(from,to); add(to,from); // 双向边
    }
    
    bfs(1); // 对a树进行lca的预处理,根为1 	    
        
	dfsa(1,0);
    
    for(int i=1;i<=len;i++){
        if(mp[dfs_[i]]){
        	if(xa1==-1){
        		xa1=dfs_[i]; // 记录a的dfs序中第一个x 
        	}
        	else if(xa2==-1){
				xa2=dfs_[i]; // 记录a的dfs序中第2个x 
				break;
			}
        }
    }
    for(int i=len;i>=1;i--){
        if(mp[dfs_[i]]){
        	if(lastxa==-1){
        		lastxa=dfs_[i]; // 记录a的dfs序中最后一个x 
        	}
        	else if(xa3==-1){
				xa3=dfs_[i]; // 记录a的dfs序中倒数第2个x 
				break;
			}
        }
    }
	
	ll t1=a[lca(xa1,lastxa)]; // 两边的lca结点的权重
	for(int i=1;i<=k;i++){ // 枚举删除每个结点 
		if(x[i]==xa1){
			ansa[i]=a[lca(xa2,lastxa)];
		}
		else if(x[i]==lastxa){
			ansa[i]=a[lca(xa1,xa3)];
		}
		else{ // 删一个中间结点,lca值是两边的lca结点的权重 
			ansa[i]=t1;
		}
	}
	
	// 初始化 
	memset(h, -1, sizeof h);  // 初始化头节点
	memset(e,0,sizeof e);
	memset(ne,0,sizeof ne);
	idx=0;
	memset(f,0,sizeof f);
	memset(d,0,sizeof d);
    
	len=0;
    for(int i=1;i<=n-1;i++){
        int from,to;
        from=i+1; to=pb[i];
        
        gb[from].push_back(to);
        gb[to].push_back(from); // 双向边 
        
        add(from,to); add(to,from); // 双向边
    }
    bfs(1); // 对b树进行lca的预处理,根为1 	    
    
	dfsb(1,0);
	
    for(int i=1;i<=len;i++){
        if(mp[dfs_[i]]){
        	if(xb1==-1){
        		xb1=dfs_[i]; // 记录b的dfs序中第一个x 
        	}
        	else if(xb2==-1){
				xb2=dfs_[i]; // 记录b的dfs序中第2个x 
				break;
			}
        }
    }
    for(int i=len;i>=1;i--){
        if(mp[dfs_[i]]){
        	if(lastxb==-1){
        		lastxb=dfs_[i]; // 记录b的dfs序中最后一个x 
        	}
        	else if(xb3==-1){
				xb3=dfs_[i]; // 记录b的dfs序中倒数第2个x 
				break;
			}
        }
    }
    
	t1=b[lca(xb1,lastxb)]; // 两边的lca结点的权重
	for(int i=1;i<=k;i++){ // 枚举删除每个结点 
		if(x[i]==xb1){
			ansb[i]=b[lca(xb2,lastxb)];
		}
		else if(x[i]==lastxb){
			ansb[i]=b[lca(xb1,xb3)];
		}
		else{ // 删一个中间结点,lca值是两边的lca结点的权重 
			ansb[i]=t1;
		}
	}
	
	// 树上多个点的LCA,就是DFS序最小的和DFS序最大的这两个点的LCA。
	ll ans=0; // 答案为删除一个数的解的数量
	
	for(int i=1;i<=k;i++){ // 枚举删除每个结点 
		if(ansa[i]>ansb[i]){
			ans++;
		}
	}
	
	cout<<ans<<endl;
}
/*
5 3
5 4 3
6 6 3 4 6
1 2 2 4
7 4 5 7 7
1 1 3 2

*/ 

进阶题

J题 Journey / 旅行

题目大意
给定若干个十字路口,右转不需要等红灯,直行、左转和掉头都需要等一次红灯。
求起点到终点最少等几次红灯。

考察内容
最短路径,dijkstra算法,双端队列bfs

分析
方法一(更直观):
把每条路看做点,十字路口处连边,形成一个边权为0/1的有向图。然后dijkstra算法求最短路。

方法一代码:

#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
typedef tuple<int, int, int> tup;
const int N = 1000005;
int n, m, cross[N][4], dis[N][4];
bool vis[N][4];
unordered_map<int, int> id[N];
int st1, st2, en1, en2;
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    // 读入数据 
    cin >> n;
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j < 4; j++) {
            cin >> cross[i][j];
            if (cross[i][j]) id[i][cross[i][j]] = j;
        }
    }
    cin >> st1 >> st2 >> en1 >> en2;
    
    memset(dis, 0x3f, sizeof(dis));
    int idx = id[st2][st1];
    dis[st2][idx] = 0;
    priority_queue<tup, vector<tup>, greater<tup>> PQ;
    
    tup t0(0, st2, idx);
    PQ.push(t0);
//    PQ.push({0, st2, idx});
    
	while (!PQ.empty()) {
     	tup t1=PQ.top();
		int d=get<0>(t1), u=get<1>(t1), x=get<2>(t1);
	//	auto [d, u, x] = PQ.top();
        
        PQ.pop();
        if (vis[u][x]) continue;
        vis[u][x] = true;
        for (int i = 0; i < 4; i++) {
            int delta = (i != (x + 1) % 4);
            int v = cross[u][i];
            if (!v) continue;
            int j = id[v][u];
            if (d + delta < dis[v][j]) {
                dis[v][j] = d + delta;
                
				tup t2(dis[v][j], v, j);
				PQ.push(t2);
			//    PQ.push({dis[v][j], v, j});
            }
        }
    }
    int ans = dis[en2][id[en2][en1]];
    if (ans > n * 4) {
        ans = -1;
    }
    cout << ans; 
}

方法二(更快):
不建图,直接在所给数据上用双端队列BFS解决,用一个deque维护队列。

方法二代码:

#include<bits/stdc++.h>
using namespace std;
using pii=pair<int,int>;
const int maxn=5e5+7;
struct node{int x,y,step;};
int n,sx,sy,tx,ty,ans,p[5];
int v[maxn][4],mp[maxn][4];

int main(){
	// 读入数据 
	cin>>n;
	for(int i=1;i<=n;i++)
		for(int j=0;j<4;j++){
			cin>>v[i][j];
			mp[i][j]=maxn;
		}
	cin>>sx>>sy>>tx>>ty;
	
	deque<node> q; // 双端队列 
	q.push_front({sx,sy,0});
	while(q.size()){
		node n1=q.front();
		int x=n1.x, y=n1.y, step=n1.step;
		
		q.pop_front();
		if(y==0)
			continue;
		int j;
		for(int i=0;i<4;i++){
			if(v[x][i]!=y)
				continue;
			j=i;
		}
		if(mp[x][j]<=step)
			continue;
		else
			mp[x][j]=step;
		for(int i=0;i<4;i++){
			if(v[y][i]!=x)
				continue;
			j=(i+1)%4; // 右转 
		}
		for(int i=0;i<4;i++){
			if(i==j)
				q.push_front({y,v[y][i],step}); // 右转的放队列前面 
			else
				q.push_back({y,v[y][i],step+1}); // 其他的方向步数+1,并且放队列后面 
		}
	}
	
	for(int i=0;i<4;i++)
		if(v[tx][i]==ty)
			ans=mp[tx][i];
			
	if(ans==maxn)
		ans=-1;
	cout<<ans<<"\n";
	return 0;
}

H题 Hacker / 黑客

题目大意
给出长度为 n n n 的小写字符串 A A A k k k 个长度为 m m m 的小写字符串 𝐵 1 … 𝐵 𝑘 𝐵_1…𝐵_𝑘 B1Bk 𝐵 𝐵 B 的每个位置拥有统一的权值 𝑣 1 … 𝑣 𝑚 𝑣_1…𝑣_𝑚 v1vm

对每个 𝐵 𝑖 𝐵_𝑖 Bi 求最大区间和,要求该区间构成的字符串是 A A A 的子串。空区间合法。

考察内容
字符串匹配,后缀自动机,最大区间和

分析
我们可以将问题进行转化,相当于对 𝐵 𝑖 𝐵_𝑖 Bi 的每个位置求出它作为结束位置在 A A A 中的最长子串长度,然后在该区间求最大子段和,所有位置的最大值即为答案。
对于每个位置的最长子串,可以对 A A A 建后缀自动机,然后 𝐵 𝑖 𝐵_𝑖 Bi 从左往右在 A A A 的后缀自动机上转移,如果当前节点无法转移跳至父亲节点,最后无法转移则长度为0,转移成功则为转移前节点的最大长度+1。

后缀自动机传送门

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int maxn=1e6+5,maxm=1e6+5;
struct ad{
	int ch[26],len,fa;
}node[maxn];
int n,m,k,lst=1,tot=1,b[maxn];
char s[maxn],a[maxm];
inline int read(){
	int d=0,f=0,c=getchar();
	for(;c<48||c>57;c=getchar()) f|=c=='-';
	for(;c>47&&c<58;c=getchar()) d=(d<<1)+(d<<3)+(c^48);
	return f?-d:d;
}
void add(int x){
	int p=lst,np=lst=++tot,q,nq;
	node[np].len=node[p].len+1;
	for (;p&&!node[p].ch[x];p=node[p].fa) node[p].ch[x]=np;
	if (!p) node[np].fa=1;
	else{
		q=node[p].ch[x];
		if (node[q].len==node[p].len+1) node[np].fa=q;
		else{
			nq=++tot;node[nq]=node[q];node[nq].len=node[p].len+1;
			for (;p&&node[p].ch[x]==q;p=node[p].fa) node[p].ch[x]=nq;
			node[q].fa=node[np].fa=nq;
		}
	}
}
int main(){
	// 读入数据 
	n=read();m=read();k=read();
	scanf("%s",s+1);
	for (int i=1;i<=n;++i) add(s[i]-'a');
	for (int i=1;i<=m;++i) b[i]=read();
	
	while (k--){
		scanf("%s",a+1);
		ll ans=0,sum=0,p=1;
		for (int i=1;i<=m;++i){
			if (sum+b[i]>=0&&node[p].ch[a[i]-'a']){
				p=node[p].ch[a[i]-'a'];
				sum+=b[i];
			} 
			else{
				p=1;
				sum=0;
			} 
			ans=max(ans,sum);
		}
		printf("%lld\n",ans);
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值