2022牛客寒假算法基础集训营3 补题题解(FK未补)


过了四题,第一次排名进前1000,打比赛确实可以带来进步,继续加油!

比赛传送门

官方题解


C 智乃买瓜(another version)

原题传送门
官方题解:
在这里插入图片描述

#include<bits/stdc++.h>

using namespace std;

typedef long long ll ;

const int N = 1e3 + 10, mod = 1e9 + 7;

ll dp[N];
int n, m;
vector<int> ans;

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	dp[0] = 1;
	cin>>m;
	
	for(int i = 1; i <= m; i ++ ){
		cin>>dp[i];
	}
	
	for(int i = 1; i <= m; ++ i){
		//这里的i代表的是基础质量,就是说如果存在dp[i],那么说明得出dp[m]的过程中用到了dp[i],进而就考虑dp[i]和那些数相加得到了dp[m] 
		while(dp[i]){
			ans.push_back(i * 2);//因为质量都是偶数,所以都考虑用了一半的 
			for(int j = 0; j <= m; ++ j){//这里的j枚举的是质量 
				if(i + j <= m) dp[i + j] -= dp[j];
				//此时背包的基础容量是i,如果i+j<=m就代表m可以装下i+j质量的西瓜,考虑方案数dp[i]时考虑了dp[j],所以要减去dp[j]的方案数
				while(i + j <= m && dp[i + j] < 0) dp[i + j] += mod;
				
				if(i + i + j <= m) dp[i + i + j] -= dp[j];
				//此时背包的基础容量是i,如果i+i+j<=m就代表m可以装下i+i+j质量的西瓜,考虑方案数dp[i]时考虑了dp[j],所以要减去dp[j]的方案数 
				while(i + i + j <= m && dp[i + i + j] < 0) dp[i + i + j] += mod;
			}
		}
	}
	
	cout<<ans.size()<<endl;
	for(int i = 0; i < ans.size(); ++ i){
		cout<<ans[i]<<" ";
	}
	return 0;
}

E 智乃的数字积木(easy version)

原题传送门
官方题解:
官方题解

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10, mod = 1e9 + 7;

int n, m, k;
int col[N];
int nu[N];
char s[N];
int P, Q;

ll cal(){
	for(int l = 1, r = 1; l <= n; l = r + 1, r = l){
		//数组切段,将一块连续区间内的颜色相同的区域从大到小进行排序,因为一直移动的是l,所以要保证l<=n
		while(r < n && col[r + 1] == col[l]) ++ r;
		//col记录的是颜色,当区间右端一格的颜色和区间左端颜色相同时,就将区间右端继续拓展一格,但延伸的长度不能超过总区间 
		sort(s + l, s +  1 + r, [](const char&A, const char&B){
			return A > B;//升序排序 
		});
	}
	ll res = 0;
	for(int i = 1; i <= n; ++ i){
		res = res * 10 + (s[i] - '0');
		res %= mod;
	}
	return res;
}

void Ran(int u, int v){//将所有u染色成v 
	for(int i = 1; i <= n; ++ i){
		if(col[i] == u) col[i] = v;
	}
}

int main()
{
	cin>>n>>m>>k;
	scanf("%s", s + 1);
	
	for(int i = 1; i <= n; ++ i){
		cin>>col[i];
	}
	
	cout<<cal()<<endl;
	
	while(k -- ){
		cin>>P>>Q;
		Ran(P, Q);
		cout<<cal()<<endl;
	}
	return 0;
}

G 智乃的树旋转(easy version)

原题传送门
官方题解:
在这里插入图片描述
请添加图片描述

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 1e3 + 10;

struct Node{
	int fa;
	int ch[2];
}t1[N], t2[N];


int n;

int main()
{
	cin>>n;
	
	for(int i = 1; i <= n; ++ i){
		int a, b;
		cin>>a>>b;
		t2[i].ch[0] = a;
		t2[i].ch[1] = b;
		if(a) t2[a].fa = i;
		if(b) t2[b].fa = i;
	}
	
	for(int i = 1; i <= n; ++ i){
		int a, b;
		cin>>a>>b;
		t1[i].ch[0] = a;
		t1[i].ch[1] = b;
		if(a) t1[a].fa = i;
		if(b) t1[b].fa = i;
	} 
	

	for(int i = 1; i <= n; ++ i){
		for(int j = 1; j <= n; ++ j){
			if(t1[i].fa == j && t2[j].fa == i){
				cout<<1<<endl;
				cout<<i<<endl;
				return 0;
			}
		}
	}
	cout<<0<<endl;
}

H 智乃的树旋转(hard version)

原题传送门
官方题解:
官方题解

#include<bits/stdc++.h>

using namespace std;

const int N = 1e3 + 10;

struct Tree{
	int fa;
	int ch[2];
}a[N], t[N];

int n;
vector<int>ans;
bool vis[N];


int input(Tree* t, int n){
	vector<bool> v(n + 1, true);
	int x, y;
	for(int i = 1; i <= n; ++ i){
		cin>>x>>y;
		v[x] = v[y] = false;//只有根节点没有进行过这个操作 
		t[x].fa = i;
		t[y].fa = i;
		if(x) t[i].ch[0] = x;
		if(y) t[i].ch[1] = y;
	}
	for(int i = 1; i <= n; ++ i){
		if(v[i]) return i;//最终返回的是根节点的下标 
	}
	return 0;
}

void col(int root){
	
	int da = t[root].fa;
	int dada = t[da].fa;
	int t1 = (root == t[da].ch[0]);//root为父节点的左子节点时t1为1 
	int t2 = (da == t[dada].ch[0]);//da为父节点的左子节点时t2为1 
	int h1 = t[root].ch[0 ^ t1];//h1就是树旋转时应该移动的哪个节点,如果root是父节点的左节点,ch就是root的右节点,反之相同 
	//下面这些代码段的含义可根据题中的动图理解 
	t[root].fa = dada;
	t[dada].ch[1 ^ t2] = root;
	t[da].ch[1 ^ t1] = h1;
	t[h1].fa = da;
	t[root].ch[0 ^ t1] = da;
	t[da].fa = root;
}

void splay(int root){
	//当对变化后的树进行节点旋转至原树状态时,下面while括号内的条件将不满足 
	while(!vis[t[root].fa]){//注意这里是变形之后的树 
	//注意这里是while,就是说root会一直被旋转,直到他被旋转到和原树相同的节点位置 
	//只有被旋转到和原树相同位置的节点的vis值为1 
		ans.push_back(root);//将这一步旋转的轴加入ans 
		col(root);//当root没有到原树位置时,就会一直作为旋转轴进行旋转 
	}
}

void dfs(int root){
	splay(root);
	vis[root] = true;//代表这个点已经旋转到原树位置 
	if(a[root].ch[0]) dfs(a[root].ch[0]);
	if(a[root].ch[1]) dfs(a[root].ch[1]);
	return ; 
}

//代码整体的逻辑:先从原树的根节点开始深搜,旋转时对变化后的树进行旋转操作,当原根节点置于根节点之后,就将他的vis标记更新为true,表示已经到达原位置
//然后按原树的顺序继续深搜,当把原根节点的左子节点(假设为i)以自身为轴旋转至原根节点的左子节点时(此时原根节点就是现根节点),
//因为i的父节点已经打上了标记,所以不会继续进行旋转,此时i已经恢复到原树的位置,将i也打上标记,继续沿子节点进行深搜 
// 这样每一个节点进行旋转操作时,都已经保证他的父节点已经旋转到原树的位置,一层一层下来,所有的节点就都能回复原位
//当旋转到原树的最后一个子节点时,整个树已经恢复原状,且每个节点旋转的次数不会超过N次,所以总体操作数不会超过N^2 

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	//在进行旋转操作时,一直是按照变形前的树的顺序进行旋转,转动的是变形之后的树,这就是说
	//将变形之后的树按照变形之前的树的顺序依次进行旋转,将每个节点都旋转到原树的位置 
	cin>>n;
	vis[0] = true;//0节点不能用于搜索,作为根节点搜索时的边界 
	int rta = input(a, n);
	int rtt = input(t, n);
	
	dfs(rta);//深搜变形之前的树 
	
	cout<<ans.size()<<endl;
	
	for(int i = 0; i < ans.size(); ++ i){
		cout<<ans[i]<<endl;
	}
	return 0;
}

I 智乃的密码

原题传送门
官方题解:
在这里插入图片描述

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 1e5 + 10;

int l, r, n;
int limit[4];//记录四种密码元素的最短区间左边界
int a[4][N];
char s[N];
ll ans;

int type(char c){
	if(c >= 'A' && c <= 'Z') return 1;
	else if(c >= 'a' && c <= 'z') return 2;
	else if(c >= '0' && c <= '9') return 3;
	else return 0;
}

void presum(int a[]){
	for(int i = 1; i <= n; ++ i){
		a[i] += a[i - 1];
	}
} 

int has(int a[], int l, int r){//判断l~r还有没有a[]对应的元素 
	return a[r] - a[l - 1] > 0; 
}

int get_d(){
	int temp[4];
	memcpy(temp, limit, sizeof temp);//此时limit内装的是i对应的四种密码的最小包含区间 
	//temp[0]记录的是包含大写字母的区间的左端点 
	//temp[1]记录的是包含小写字母的区间的左端点
	//temp[2]、temp[3]此时的含义与上同 
	sort(temp , temp + 4);
	//排完序后
	//temp[0]记录的是四个最小区间中最靠左的左端点,因为只需三种必要元素就可以得到密码,所以temp[0]战略性放弃 
	return temp[1];//返回可以构成密码的最小区间的左端点 
}

int cal(int i){
	int L = 1, R = n;
	L = max(L, i - r + 1);//L此时记录的是密码长度最长的情况下最左端的端点值 
	R = min(R, i - l + 1);//R此时记录的是密码长度最短的情况下最左端的端点值 
	R = min(get_d(), R);//R此时的值更新为能构成区间的最左端的点 
	return R - L >= 0 ? R - L + 1 : 0;//R-L>0表示以i为右端点,向左延伸长度为r的字符串中,左边部分的无关元素数量大于0,这些无关元素的数量就是可以构成的密码数量
	//如果五官元素的数量小于0,表示不能构成密码,返回构成的密码数量为0 
}

//妙在利用a数组,以前缀和的形式判断这个区间内有没有密码对应的元素 

int main()
{
	cin>>n>>l>>r;
	scanf("%s", s + 1);
	
	for(int i = 1; i <= n; ++ i){
		a[type(s[i])][i] ++ ;//标记s[i]是那种密码 
	}
	
	for(int i = 0; i < 4; ++ i){
		presum(a[i]);//找出每种密码的数量前缀和 
	}
	
	for(int i = 1; i <= n; ++ i){
		for(int j = 0; j < 4; ++ j){//分别判断这四种密码的最小存在区间的区间左端点 
			while(has(a[j], limit[j] + 1, i)) limit[j] ++ ;//字符串是从1开始记录的,limit数组内的值默认为0,应该+1开始 
		}
		//分别找到i对应的包含四种密码的最短区间 
		ans += cal(i);//找到以i为右端点的合法的密码数量,遍历每个i,加在一起就是总的密码数量 
	}
	cout<<ans<<endl;
	return 0;
}

J 智乃的C语言模除方程

原题传送门
这个解题思路是看一个大佬的,比官方要简洁的多
大佬原题解

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

int n;
int p, l, r, L, R;
ll ans;

int J(int a, int b, int c, int d){//寻找[a ,b]和[c, d]的区间交集长度 
	if(c > b || d < a) return 0;
	else return min(b, d) - max(a, c) + 1; 
}

int qoq(int v){//查询0~v有多少个数出现过,分为两个部分,一个是0~p-1,个数=v%p,另一部分是p~v,个数是v/p*(p-1) 
	if(v < 0) return (int)abs(v / p) * J(-p + 1, 0, l, r) + J(v % p, 0, l, r);
	else return (int)(v / p) * J(0, p - 1, l, r) + J(0, v % p, l, r);
}

int main()
{
	cin>>p>>l>>r>>L>>R;
	p = abs(p);
	
	//下面代码段利用前缀和画一画就明白 
	if(L <= 0 && R >= 0) ans = qoq(L) + qoq(R) - qoq(0);
	else if(L < 0 && R < 0) ans = qoq(L) - qoq(R + 1) ;
	else ans = qoq(R) - qoq(L - 1);
	
	cout<<ans<<endl;
	
	return 0;
}

总结

水平有限,K和F先不补了,以后再说 这次比赛能看到自己这几天的进步,前几天也懒了不少,就当歇歇了。既然尝到了甜头就继续坚持下去吧,ACACACACACACAC
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值