“蔚来杯“2022牛客暑期多校训练营9 补题题解(A、B、G、E)

本文解析了2022牛客暑期多校训练营中的四道题目:CarShow的双指针优化、TwoFrogs的动态规划与逆元应用、MagicSpells的回文自动机与马拉车算法,以及LongestIncreasingSubsequence的构造与优化技巧。通过实例展示了信息技术在实际问题中的应用。
摘要由CSDN通过智能技术生成

“蔚来杯“2022牛客暑期多校训练营9


A Car Show

双指针算法,先移动右指针到满足从1~ i的区间内1~m的数每个至少有一个,之后移动左指针,只要满足区间内1 ~ m的数每个至少还有一个,左指针j就能一直移动,直到不满足条件。
这题还有个要注意的点是需要define intg long long,但是题中给的n、m、a都是小于1e6的,唯一可能的就是ans会爆int。

#include<bits/stdc++.h>

using namespace std;

#define int long long

const int N = 1e5 + 10;

int n, m;
int a[N];
int ans;  //记录答案
int res;  //记录不同数的个数
int cnt[N];  //记录每个数出现过没 

signed main()
{
	cin>>n>>m;
	
	for(int i = 1; i <= n; i ++ ){
		cin>>a[i];
	}
	
	int j = 1;  //左指针 
	for(int i = 1; i <= n; i ++ ){  //移动区间右指针 
		if(!cnt[a[i]]){
			res ++ ;
		}
		cnt[a[i]] ++ ;
		if(res == m){
			while(j < i && cnt[a[j]] > 1){
				ans += n - i + 1;
				cnt[a[j]] -- ;
				j ++ ;  //移动左指针 
			}
			ans += n - i + 1;  //这个加上的是最后一个符合条件的区间,此时a[j]=1,在while不会被计算 
			cnt[a[j]] -- ;
			j ++ ;
			res -- ;
		}
	}
	cout<<ans<<endl;
	return 0; 
} 

B Two Frogs

请添加图片描述

#include<bits/stdc++.h>

using namespace std;

#define int long long

const int N = 8010, mod = 998244353;

int dp[N][N];  //dp[i][j]表示跳了i次到达j点的概率
int a[N];
int n;
int infac[N];  //逆元数组 
int ans;
int di[N];  //一维差分数组

int qmi(int a, int b){
	int res = 1;
	while(b){
		if(b & 1) res = res * a % mod;
		a = a * a % mod;
		b >>= 1; 
	}
	return res;
}

void init(){  //求逆元 
	for(int i = 1; i < 8000; i ++ ){
		infac[i] = qmi(i, mod - 2); 
	}
}

signed main()
{
	init();
	cin>>n;
	for(int i = 1; i < n; i ++ ) cin>>a[i];
	
	dp[0][1] = 1;
	
	for(int i = 1; i < n; i ++ ){  //枚举起点 
		
		for(int j = 1; j < n; j ++ ){   
			di[j + 1] = (di[j + 1] + dp[i - 1][j] * infac[a[j]] % mod + mod) % mod;
			di[j + a[j] + 1] = (di[j + a[j] + 1] - dp[i - 1][j] * infac[a[j]] % mod + mod) % mod;
		}
		
		for(int j = 1; j <= n; j ++ ){  //这里是在对到达i点的所有步骤进行差分求前缀和  
			dp[i][j] = (dp[i][j] + dp[i][j - 1] + di[j]) % mod;
		}
		
		memset(di, 0, sizeof di);
	}
	
	for(int i = 0; i < n; i ++ ){  //同时到达0点也包括在内 
		ans = (ans + dp[i][n] * dp[i][n] % mod) % mod;
	}
	
	cout<<ans<<endl;
	
	return 0;
}

G Magic Spells

一道题搞了两天,终于搞懂了回文自动机,还学会了马拉车算法,也算收货不小,这题是回文自动机套板子
回文自动机讲解看这里

#include<bits/stdc++.h>

using namespace std;

const int N = 3e5 + 10;

int tr[N][27];  //建树
int n;
int ans;  //记录答案
bool cnt[5][N];  //cnt[i][j]表示在第i个魔法串内j串是否存在
int fail[N];  //fail[i]指向i的最长回文后缀子串的节点(个人认为是回文树的灵魂数组)
int len[N];  //记录节点长度
int idx;  //记录节点个数
int last;  //记录上一个新加入的节点
char s[N];  //存储字符串
int now;

int newnode(int x){  //创建新节点 
	len[idx] = x;
	return idx ++ ;  //返回之后idx再++ 
} 

int getfail(int x, int n){  跳fail指针直到找到后缀回文为止,用到的两个参数都是字符串的下标 
	while(s[n - len[x] - 1] != s[n]) x = fail[x];
	return x;
}

void init(){
	newnode(0), newnode(-1);
	fail[0] = 1;	
}

void insert(int j, int i){
	int t = s[j] - 'a';
	//找到可以回文的位置
	int p = getfail(last, j);  //返回的是节点编号
	if(!tr[p][t]){
		//如果有了转移就不用建了,否则要新建 
	    //前后都加上新字符,所以新回文串长度要加2
		now = newnode(len[p] + 2);
		//因为fail指向的得是原串的严格后缀,所以要从p的fail开始找起
		fail[now] = tr[getfail(fail[p], j)][t];
		//记录转移
		tr[p][t] = now;
	}
	//以下两个变量不能直接用now赋值,因为tr[p][t]可能存在,此时now的值就会赋错 
	last = tr[p][t];
	cnt[i][last] = true;
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	cin>>n;
	
	init();  //初始化一定不能忘了
	 
	//先把所有串放到一个树内
	for(int i = 0; i < n; i ++ ){
		last = 0;
		cin>>s + 1;
		s[0] = -1;
		for(int j = 1; s[j]; j ++ ) insert(j, i);
	}
	
	for(int i = idx - 1; i > 1; i -- ){  //遍历除两个根节点之外的每个节点,沿着树从下往上遍历,因为树上层的状态没有被更新 
		for(int j = 0; j < n; j ++ ){  //遍历n个串
			cnt[j][fail[i]] |= cnt[j][i];
		}
		
		bool ok = true;
		
		for(int j = 0; j < n; j ++ ){
			if(!cnt[j][i]) ok = false;
		}
		ans += ok;
	}
	
	cout<<ans<<endl;  //答案是所有串中都包含的回文子串的个数 
	
	return 0;
}

E Longest Increasing Subsequence

构造类型题,先想最基础的构造,然后根据题意逐步优化
雨巨讲题 从39min开始看
代码是借鉴这个大佬的:这个讲解也很强,和雨巨的结合着更容易理解 传送门

#include<bits/stdc++.h>

using namespace std;

const int N = 1e6 + 10;

int T;
int n;
int len;  //长度
int cnt;  //排列的长度
int cur;  //赋值游标
int ans[N];  //答案数组

int main()
{
	cin>>T;
	while(T -- ){
		cnt = 0;
		len = 0;	
		cin>>n;
		if(n == 1){
			cout<<1<<endl<<1<<endl;
			continue;
		}
		for(int i = 31; i; i -- ){  //从高位向低位遍历 
			if(n >> i & 1){  找到第一个1,这是要个构建的基础组数(一组两个数,如21、43、65) 
				len = i;
				break;
			}
		}
		
		cur = 2 * len;
	//	cout<<cur<<' '<<len<<"***"<<endl;
		for(int i = 0; i < len; i ++ ){
			if(n >> i & 1){  //如果遇到要补的1
				ans[ ++ cnt] = ++ cur;
				int x = i + 1;
				while((1 << x) <= n && !((n >> x) & 1)){  //这里循环的次数就是i到更高位1之间0的数量+1 
					ans[ ++ cnt] = ++ cur;
					x ++ ; 
				//	cout<<"x:"<<x<<endl;
				}
			}
			ans[ ++ cnt] = 2 * i + 2;
			ans[ ++ cnt] = 2 * i + 1;
		}
		
		cout<<cnt<<endl;
		for(int i = 1; i <= cnt; i ++ ){
			cout<<ans[i]<<' ';
		}
		cout<<endl;
	}
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值