“蔚来杯“2022牛客暑期多校训练营7 补题题解(C、F、G、J、K)


比赛地址

C Constructive Problems Never Die

请添加图片描述

#include<bits/stdc++.h>

using namespace std;

#define x first
#define y second

typedef vector<int> ve;
typedef pair<int, int> PII;

const int N = 1e5 + 10;

int T;
int n;
int p[N];  //原数组
int ans[N];  //答案数组
int vis[N];  //标记这个数有没有被放置

ve g[N];  //记录数和出现的位置

void solve(){
	cin>>n;
	
	//注意memset比较耗时 
	for(int i = 1; i <= n; i ++ ){  //清空状态 
		g[i].clear();
		ans[i] = 0;
		vis[i] = 0;
	} 	
	

	for(int i = 1; i <= n; i ++ ){
		cin>>p[i];
		g[p[i]].push_back(i);  //g[a[i]]记录了p[i]都在哪个位置出现
	} 
	
	vector<PII> v;  //一维记录数,二维记录数出现的位置
	
	for(int i = 1; i <= n; i ++ ){
		if(g[i].size()){  //如果这个数出现过 
			v.push_back({i, *g[i].begin()});  //记录这个数出现过的第一个位置 
		}
	}
	
	if(v.size() == 1){  //如果只有1个数出现过,无法构成题中所需要的排列,那就直接输出0 
		cout<<"NO"<<endl;
		return ;
	} 
	
	for(int i = 1; i < v.size(); i ++ ){  //遍历出现过的每个数 
		ans[v[i].y] = v[i - 1].x;  //ans[i]记录第i个位置 = 上一个出现过的数的数值
		//按照解释相当于把2放到了1的位置上 因为v只记录了某个数出现的第一个位置,所以这样放一定不会出现重复 
		vis[v[i - 1].x] = 1;  //标记这个数已经被用过了 
	} 
	//begin和end是指针,指向位置,back指向最后一个元素,是数值 
	ans[v.begin()->y] = v.back().x;  //把v里面第一个数出现的位置放上最后一个出现的数 
	vis[v.back().x] = 1;  //标记这个数被放过
	
	cout<<"YES"<<endl;	
	
	int id = 1;  //从第一个数开始,id表示的是放置进排列的数
	for(int i = 1; i <= n; i ++ ){
		if(ans[i] == 0) {  //如果这个位置还没有存过数 
			while(vis[id] == 1) ++ id;  //向后遍历,找到第一个没有被放过位置的数(相当于从小到大放数) 
			ans[i] = id;  //把这个数放置 
			vis[id] = 1;  //标记这个数已经放过 		
		}		
	} 

	for(int i = 1; i <= n; i ++ ) cout<<ans[i]<<' ';
	cout<<endl;
	
	return ;	
} 

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0); 
	
	cin>>T;
	while(T -- ){
		solve();
	}
}

F Candies

请添加图片描述

#include<bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;

int n, x;
int a[N];
int stk[N], top;  //模拟栈
int ans;  //记录答案

int main()
{
	cin>>n>>x;
	
	for(int i = 1; i <= n; i ++ ){
		cin>>a[i];
	}
	
	for(int i = 1; i <= n; i ++ ){
		if(!top) stk[ ++ top] = a[i];
		else if(stk[top] == a[i]) top -- , ans ++ ;
		else if(stk[top] + a[i] == x) top -- , ans ++ ;
		else stk[ ++ top] = a[i];
	}
	
	for(int i = 1; i <= top / 2; i ++ ){  //因为有这一步栈内配对,所以只能用手写模拟栈 
		if(stk[i] == stk[top - i + 1]) ans ++ ;
		else if(stk[i] + stk[top - i + 1] == x) ans ++ ;
		else break;  //一旦有环的两端无法匹配,那剩下的元素就不可能相邻,所以直接退出 
	}
	cout<<ans<<endl;
	
	return 0;
} 

G Regular Expression

雨巨说的对,这题最难得确实是读题
正则表达式-B站大佬讲
这题光搞懂正则表达式的匹配机制搞了半天,这里记录一下字符匹配规则
.:英文句号,可以表示任意字符,但不包含换行符
?:表示?的前一个字符需要出现0次或1次,例如:used?可以匹配 use和used
:星号表示前一个字符可以出现0次或多次(注意不能是1次),例如abc可以匹配ac、abbc、abbbbbbc
+:表示可以匹配出现一次及以上次数的字符,比如ab+c可以匹配abc、abbbc、abbbbc
{}:ab{2,6}c表示b可以出现2~6次,ab{2}c表示b可以出现2次
():表示可以把圈起来的字符串看成一个整体被限定符所作用
|: 表示或
明白了这些字符串的意义之后,就知道这是一个分类讨论的题,这里借鉴一下大佬写的题解
请添加图片描述

#include<bits/stdc++.h>

using namespace std;

int T;

int main()
{
	cin>>T;
	while(T -- ){
		string str;
		cin>>str;
		
		int len = str.length();
		
		if(len == 1) cout<<"1 2"<<endl;
		else if(len == 2){
			if(str[0] == str[1]) cout<<"2 8"<<endl;
			else cout<<"2 6"<<endl;
		}
		else{
			bool flag = true;
			for(int i = 0; i < len - 1; i ++ ){
				if(str[i] != str[i + 1]){  //存在不相同的 
					flag = false;
					break;
				}
			}
			
			if(!flag) cout<<"2 2"<<endl;
			else cout<<"2 4"<<endl;
		}
	}
	return 0;
}

J Melborp Elcissalc

这题怎么说呢,特别不理解,看来雨巨讲题,又看了好几份题解才勉勉强强理解,但是还是感觉太绕了,两个理解思路:
第一个:雨巨的 请添加图片描述
第二个:
请添加图片描述

#include<bits/stdc++.h>

using namespace std;

#define int long long 

const int N = 110; 

int dp[N][N][64 * 40];
int n, k, t;
int C[2010][2010];  //表示 
int mod = 998244353;

void init(){
	for(int i = 0; i <= 210; i ++ ){
		for(int j = 0; j <= i; j ++ ){
			if(!j) C[i][j] = 1;
			else C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
		}
	} 
}

signed main()
{
	init();
	cin>>n>>k>>t;
	
	dp[0][0][0] = 1;
	
	for(int i = 1; i <= k; i ++ ){
		for(int j = 0; j <= n; j ++ ){
			for(int p = 0; p <= t; p ++ ){
				if(dp[i - 1][j][p] == 0) continue;
				for(int l = 0; l + j <= n; l ++ ){  //l就是i放的个数,可以一个也不放 
					//乘上C[j + l][l]是因为:对于已经构造好的前j位前缀和数组来说,又新增了l个位置,从这些位置中选出l个位置
					//新选位置的方法与原来前j个序列只放数字(1~i-1)相乘就是新的方案数 
					if(i == 1){  //当i是1时,上一个状态i就是0,如果这一位放入0,那么区间的增长个数就是C[l + 1][2] 
						(dp[i][j + l][p + C[l + 1][2]] += dp[i - 1][j][p] * C[j + l][l]) %= mod;   
					}
					else (dp[i][j + l][p + C[l][2]] += dp[i - 1][j][p] * C[j + l][l]) %= mod;
				}
			}
		}
	}
	cout<<dp[k][n][t]<<endl;
	return 0;
}

K Great Party

先推出奇数情况下先手必胜,类比只有一堆时,先手必胜,偶数堆时,就是Nim游戏,如果区间内所有数的异或和是0,那么就是先手必败,否则先手必胜。推出这个结论后,考虑怎么得出在多次询问下得出区间内的异或和的情况,即莫队+异或和前缀数组
大佬写的详细题解1
大佬写的详细题解2

#include<bits/stdc++.h>

using namespace std;

#define int long long 

const int N = 4e6 + 10;

struct Qurey{
	int id, l, r;
}q[N];

int sum[2][N];
int n, m;
int a[N];
int len;
int res;
int s[N];
int ans[N];

int get(int x){
	return x / len + 1;
}

bool cmp(Qurey a, Qurey b){
	int i = get(a.l), j = get(b.l);
	if(i != j) return i < j;
	return a.r < b.r;
}

int C(int x){
	return x * (x - 1) / 2;
}

void add(int x){
	res -= C(sum[x % 2][s[x]]);
	sum[x % 2][s[x]] ++ ;
	res += C(sum[x % 2][s[x]]);
}

void del(int x){
	res -= C(sum[x % 2][s[x]]);
	sum[x % 2][s[x]] -- ;
	res += C(sum[x % 2][s[x]]);
}

signed main()
{
	cin>>n>>m;
	len = sqrt(n);  //分块每个块内的元素数量 
	for(int i = 1; i <= n; i ++ ){
		cin>>a[i];
		s[i] = s[i - 1] ^ (a[i] - 1); 
	}
	
	for(int i = 1; i <= m; i ++ ){
		int l, r;
		cin>>l>>r;
		l -- ;
		q[i] = {i, l, r};
	}
	
	sort(q + 1, q + m + 1, cmp);
	
	int l = 1, r = 0;
	for(int i = 1; i <= m; i ++ ){
		int id = q[i].id, L = q[i].l, R = q[i].r;
		while(r < R) add( ++ r);
		while(r > R) del(r -- );
		while(l < L) del(l ++ );
		while(l > L) add( -- l);
		ans[id] = C(r - l + 1) - res;
	}
	
	for(int i = 1; i <= m; i ++ ) cout<<ans[i]<<endl;
	
	return 0;
}


补一场多校真累啊,尤其是今天这个K题,先学了简单的莫队才勉强明白题解的写法,不过也算收获满满,复习博弈论的同时又学了一种算法,下周末网络赛,加油吧!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值