第十四届蓝桥杯C++B组个人题解——无FJ

第十四届蓝桥杯C++B组个人题解——无FJ

本文章仅是个人题解,不能保证全对,但思路大抵就是那么个思路。今年的题感觉要比去年难了些,F想不到很好的判环方法,最后一题都不知道再说什么,图论苦手是这样的,不过其它倒是挺对我胃口。
但说实话线下在学校机房比,超级不适应啊!
(更新:编程题库 - C语言网 (dotcpp.com)这里有民间数据了,可以去测一测)

A、日期统计

问题描述

小蓝现在有一个长度为 100 的数组,数组中的每个元素的值都在 0 到 9 的范围之内。数组中的元素从左至右如下所示:

5 6 8 6 9 1 6 1 2 4 9 1 9 8 2 3 6 4 7 7 5 9 5 0 3 8 7 5 8 1 5 8 6 1 8 3 0 3 7 9 2 7 0 5 8 8 5 7 0 9 9 1 9 4 4 6 8 6 3 3 8 5 1 6 3 4 6 7 0 7 8 2 7 6 8 9 5 6 5 6 1 4 0 1 0 0 9 4 8 0 9 1 2 8 5 0 2 5 3 3

现在他想要从这个数组中寻找一些满足以下条件的子序列:

  1. 子序列的长度为 8;

  2. 这个子序列可以按照下标顺序组成一个 yyyymmdd 格式的日期,并且要求这个日期是 2023 年中的某一天的日期,例如 20230902,20231223。yyyy 表示年份,mm 表示月份,dd 表示天数,当月份或者天数的长度只有一位时需要一个前导零补充。

请你帮小蓝计算下按上述条件一共能找到多少个不同 的 2023 年的日期。对于相同的日期你只需要统计一次即可。

问题解析

答案:235

可以发现,到了第59个数字的时候,才第一次凑出2023的子序列,那么对于剩下的其它数字,可以用四个for循环来枚举mmdd,在判断dd(即天数)符不符合mm(月数),注意去重。

或者是可以直接枚举2023年的365天,即从20230101枚举到20231231为止,然后看能不能在字符串中找到这么个序列。

代码
#include<iostream>
using namespace std;
#include<cstdio>
#include<algorithm>
#include<string>
#include<unordered_map>
#include<queue>
#include<vector>
#include<stack>
#include<map>
#include<string.h>
#include<math.h>
#include<iomanip>

#define int long long
#define endl '\n'
typedef long long ll;
typedef pair<ll, ll> PII;
const int N = 1e5 + 10;
int f[100][100];
//"5686916124,9198236477,5950387581,5861830379,2705885709,919446863——38516346707827689565614010094809128502533";
void solve()
{
	map<int, int>mymap;
	mymap[1] = mymap[3] = mymap[5] = mymap[7] = mymap[8] = mymap[10] = mymap[12] = 31;
	mymap[4] = mymap[6] = mymap[9] = mymap[11] = 30;
	mymap[2] = 28;
	string s = "5686916124919823647759503875815861830379270588570991944686338516346707827689565614010094809128502533";
	string yy = "2023";
	int n = s.size(), idx = 0, cnt = 0;
	for (int i = 0; i < n; i++)
	{
		if (s[i] == yy[idx])idx++;
		if (idx == 4)
		{
			idx = i + 1;
			break;
		}
	}

	for (int i = idx; i < n; i++)
	{
		if (s[i] - '0' > 1)continue;
		for (int j = i + 1; j < n; j++)
		{
			if (s[i] - '0' == 1 && s[j] - '0' > 2)continue;
			for (int k = j + 1; k < n; k++)
			{
				for (int l = k + 1; l < n; l++)
				{
					int m = (s[i] - '0') * 10 + (s[j] - '0');
					int d = (s[k] - '0') * 10 + (s[l] - '0');
					if (d == 0)continue;
					if (mymap.count(m) && mymap[m] >= d && f[m][d] == 0)
					{
						cout << m << "月" << d << "日" << endl;
						f[m][d] = 1;
						cnt++;
					}
				}
			}
		}
	}
	cout << cnt;
}

signed main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);

	int t = 1;
	//cin>>t;
	while (t--)
	{
		solve();
	}

	return 0;
}

B、01串的熵

问题描述

屏幕截图 2023-04-08 194122.png

问题解析

答案:11027421

这题对我来说主要难点在于算log,说实话我之前真不知道有函数能直接算log,这题的log我是采用了浮点型二分的方法来算的。并且也测试了下S=100的情况发现是正确的。然后我们就枚举0的个数就好了,1的个数就是23333333-0的个数。

不过我这做法有两点要注意的:

  1. 没必要从0开始枚举0的个数,实际上debug几遍后就能发现大约在11000000左右的时候算出来的值就很接近题目的这个值了。
  2. 二分的精度要高,一开始我习惯性的设置精度为1e-6,结果咋都跑不出正确结果,后来折磨了半小时后才发现精度要开到1e-15左右才能出正确结果。哭死。

代码

#include<iostream>
using namespace std;
#include<cstdio>
#include<algorithm>
#include<string>
#include<unordered_map>
#include<queue>
#include<vector>
#include<stack>
#include<map>
#include<string.h>
#include<math.h>
#include<iomanip>


#define int long long
#define endl '\n'
typedef long long ll;
typedef pair<ll,ll> PII;
const int N=1e5+10;
int f[100][100];

//算log2(a)的值(我不会用函数啥的算,也不知道有没有现成的函数,但我知道有现成的幂运算) 
double check(double a)
{
	double l=-10,r=0;
	//精度开小了会寄掉,一开始我写的1e-6,咋都不出结果 
    while(abs(r-l)>=1e-15)
	{
	    double mid=(l+r)/2;
		if(pow(2,mid)>a)r=mid;
		else l=mid;	
	}
	return l;		
} 

//答案:11027421

void solve()
{
	//double x=check(1.0/3),y=check(2.0/3);
	//测试后,答案等于1.3083,说明方法没问题 
	//cout<<fixed<<setprecision(5)<<-1.0/3*x+-2.0/3*y-2.0/3*y;
	
	
	//没必要从0开始枚举,算的太慢了 
	for(int i=11026000;i<=23333333/2;i++)
	{
		if(i>=23333333-i)continue;
		double a=i/23333333.0,b=(23333333-i)/23333333.0;
		double x=check(a),y=check(b);
		double z=i*(-1.0*i/23333333.0*x)+(23333333-i)*(-1.0*(23333333-i)/23333333.0*y);

		if(abs(11625907.5798-z)<=1e-4)
		{
			cout<<i<<endl;
			return;
		}
		
		
	}
	
	
}

signed main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	
	int t=1;
	//cin>>t;
	while(t--)
	{
		solve();
	}
	
	return 0;
}

C、冶炼金属

屏幕截图 2023-04-08 195127.png

屏幕截图 2023-04-08 195201.png

屏幕截图 2023-04-08 195216.png

问题解析

可以看出,V可能的最大值直接就是:a/b了,没有问题。

重点是最小值,既然要转换率V最小,那就是最后浪费的尽可能的多。用a/(b+1),即算出a个普通金属如果想冶炼出b+1个特殊金属,转化率是多少。再逆过来判断一下,如果a可以通过这个转化率冶炼出b+1个金属,则V++。直到正好冶炼出b个为止,这样最后浪费的数量就是最多的了。

最后,对于所有的炼金情况,它们的最大值我们取最小,它们的最小值我们取最大(实际就是区间合并的过程)

代码

#include<iostream>
using namespace std;
#include<cstdio>
#include<algorithm>
#include<string>
#include<unordered_map>
#include<queue>
#include<vector>
#include<stack>
#include<map>
#include<string.h>
#include<math.h>
#include<iomanip>

#define int long long
#define endl '\n'
typedef long long ll;
typedef pair<ll,ll> PII;
const int N=1e5+10;

void solve()
{
	int n,a,b; 
	cin>>n;
	int mx=10000000000,mn=-1;
	for(int i=1;i<=n;i++)
	{
		cin>>a>>b;
		mx=min(mx,a/b);
		int c=a/(b+1);
		while(a/c>b)c++;
		mn=max(mn,c);
	} 
	cout<<mn<<" "<<mx;
}

signed main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);

	int t=1;
	//cin>>t;
	while(t--)
	{
		solve();
	}
	
	return 0;
}

D、飞机降落

屏幕截图 2023-04-08 195900.png

屏幕截图 2023-04-08 195944.png

屏幕截图 2023-04-08 195959.png

问题解析

一看数据,N<=10,直接枚举全部的排列次序,然后都模拟一遍,看能不能平稳落地就行。

代码

#include<iostream>
using namespace std;
#include<cstdio>
#include<algorithm>
#include<string>
#include<unordered_map>
#include<queue>
#include<vector>
#include<stack>
#include<map>
#include<string.h>
#include<math.h>
#include<iomanip>

#define int long long
#define endl '\n'
typedef long long ll;
typedef pair<ll, ll> PII;
const int N = 1e5 + 10;

int T[N], D[N], L[N];
void solve()
{
	int n;
	cin >> n;
	vector<int>v(n);
	for (int i = 1; i <= n; i++)
	{
		cin >> T[i] >> D[i] >> L[i];
		v[i-1] = i;
	}
	do {
		//记录时间
		int time = 0;
		bool flag = true;
		for (int i = 0; i < n; i++)
		{
			if (time <= T[v[i]])time = T[v[i]] + L[v[i]];
			else if (time <= T[v[i]] + D[v[i]])time += L[v[i]];
			else
			{
				flag = false;
				break;
			}
		}
		if (flag)
		{
			cout << "YES" << endl;
			return;
		}
	} while (next_permutation(v.begin(), v.end()));
	cout << "NO" << endl;
}

signed main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);

	int t = 1;
	cin>>t;
	while (t--)
	{
		solve();
	}

	return 0;
}

E、接龙数列

问题描述

屏幕截图 2023-04-08 201015.png

屏幕截图 2023-04-08 201039.png

问题解析

经典的dp问题,送分了属于是。

让我们删除最少的数,使得剩下的序列是接龙序列,实际上就是让我们求最长接龙序列的长度len,答案就是:n-len

对于这种:求最长XXX子序列的问题,一般的状态设置就是:f[i]是以i结尾的xxx序列,长度为f[i]。

那么我们就设:f[i]是以数字i结尾的最长的接龙序列,i的取值是0到9(不含前导0嘛)

first是数字a的第一个数字,last是数字a的最后一个数字,则有状态转移方程:

f[last]=max(f[last,f[first]+1);

最后取所有的序列的最大长度即可。

代码

#include<iostream>
using namespace std;
#include<cstdio>
#include<algorithm>
#include<string>
#include<unordered_map>
#include<queue>
#include<vector>
#include<stack>
#include<map>
#include<string.h>
#include<math.h>
#include<iomanip>

#define int long long
#define endl '\n'
typedef long long ll;
typedef pair<ll,ll> PII;
const int N=1e5+10;
int f[10];
void solve()
{
	int n,mx=0;
	string s;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>s;
		int a=s[0]-'0',b=s.back()-'0';
		//cout<<a<<" "<<b<<endl;
		f[b]=max(f[b],f[a]+1);
	}
	for(int i=0;i<=9;i++)
	{
		mx=max(mx,f[i]);
	}
	cout<<n-mx;
}

signed main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);

	int t=1;
	//cin>>t;
	while(t--)
	{
		solve();
	}
	
	return 0;
}

G、子串简写

问题描述

屏幕截图 2023-04-08 201828.png

屏幕截图 2023-04-08 201951.png

问题解析

又是经典的题。

先用一个数组存下来从小到大所有c2字符的下标。

然后对s的每一个c1字符,在数组里找他俩下标相减大于等于k+1的即可。这一步我们可以用二分来优化掉。

代码

#include<iostream>
using namespace std;
#include<cstdio>
#include<algorithm>
#include<string>
#include<unordered_map>
#include<queue>
#include<vector>
#include<stack>
#include<map>
#include<string.h>
#include<math.h>
#include<iomanip>

#define int long long
#define endl '\n'
typedef long long ll;
typedef pair<ll,ll> PII;
const int N=1e5+10;

void solve()
{
	int k;
	string s;
	char c1,c2;
	vector<int>v;
	cin>>k>>s>>c1>>c2;
	int n=s.size(),cnt=0;
	//注意,我这里写的下标是1
	for(int i=1;i<=n;i++)
	{
		if(s[i-1]==c2)v.push_back(i);
	}
	int len=v.size();
	//这里变回0了
	for(int i=0;i<n;i++)
	{
		if(s[i]==c1)
		{
			//二分找数组中第一个坐标和i相减大于等于k的位置
			int l=0,r=len-1;
			while(l<r)
			{
				int mid=(l+r)/2;
				//上面那俩这么做只是因为我不想写个k+1,只写个k(哈哈
				if(v[mid]>=i+k)r=mid;
				else l=mid+1;
			}
			//确保大于再算答案
			if(v[l]>=i+k)
			{
				cnt+=len-l;
			}
		}
	}
	cout<<cnt;
}

signed main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	
	int t=1;
	//cin>>t;
	while(t--)
	{
		solve();
	}
	
	return 0;
}

H、整数删除

问题描述

屏幕截图 2023-04-08 202524.png

屏幕截图 2023-04-08 202553.png

问题解析

这一题我感觉我写复杂了,但是也暂时想不到更好的点子,一开始想的链表,但我讨厌指针这玩意,所以我换了个写法。

我用的是:线段树求数组最小值+双并查集。

用一个并查集fa来链接左边的数,另一个并查集fb来链接右边的数:(如图)

未命名绘图.png

未命名绘图1.png

未命名绘图3.png

至于找数组最小值和修改数组的事情,就交给线段树就行了。

如果某个数被删除了,则在线段树中将对应位置修改成一个很大的值,不要让他影响我们的判断。

不会线段树的同学也可以看看我主页的文章,有教你如何掌握线段树(基础)。

(做法应该没啥问题,主要担心会不会写错代码+常数太大了导致tle。)

代码

#include<iostream>
using namespace std;
#include<cstdio>
#include<algorithm>
#include<string>
#include<unordered_map>
#include<queue>
#include<vector>
#include<stack>
#include<map>
#include<string.h>
#include<math.h>
#include<iomanip>
 

#define int long long
#define endl '\n'
typedef long long ll;
typedef pair<ll,ll> PII;
const int N=5e5+50;

int f[4*N],a[N],st[N],fa[N],fb[N]; 

void build_tree(int k,int l,int r)
{
	if(l==r)
	{
		f[k]=a[l];
		return;
	}
	int mid=(l+r)/2;
	build_tree(k+k,l,mid);
	build_tree(k+k+1,mid+1,r);
	f[k]=min(f[k+k],f[k+k+1]);
}

PII query(int k,int l,int r)
{
	if(l==r)
	{
		return {f[k],l};
	}
	int mid=(l+r)/2;
	PII ans={0,0};
	if(f[k+k]<=f[k+k+1])ans=query(k+k,l,mid);
	else ans=query(k+k+1,mid+1,r);
	return ans;
}

void revise(int k,int l,int r,int x,int y)
{
	if(l==r)
	{
		f[k]+=y;
		return;
	}
	int mid=(l+r)/2;
	if(x<=mid)revise(k+k,l,mid,x,y);
	else revise(k+k+1,mid+1,r,x,y);
	f[k]=min(f[k+k],f[k+k+1]);
}

int finda(int x)
{
	if(fa[x]!=x)fa[x]=finda(fa[x]);
	return fa[x];
}

int findb(int x)
{
	if(fb[x]!=x)fb[x]=findb(fb[x]);
	return fb[x];
}

void solve()
{
	int n,k;
	cin>>n>>k;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	build_tree(1,1,n);
	for(int i=1;i<=n;i++)
	{
		fa[i]=i;
		fb[i]=i;
	}

	while(k--)
	{
		//查找的结果以pair的形式返回,first是最小值,second是它在数组的下标
		PII ans=query(1,1,n);
		
		//找当前元素挨着的左边元素l,和右边元素r
		int l=finda(ans.second-1),r=findb(ans.second+1);
		//改变指向
		fa[ans.second]=l;
		fb[ans.second]=r;
		
		//修改原最小值,让它不要影响我们之后的操作
		revise(1,1,n,ans.second,1e18);
		//在线段树中修改原最小值两边的元素
		revise(1,1,n,l,ans.first);
		revise(1,1,n,r,ans.first);
		//在数组中也要修改
		a[l]+= ans.first;
		a[r]+= ans.first;
		
	}
	//输出
	for(int i=1;i<=n;i++)
	{
		//如果fa的指向没变,说明这个元素没被删
		if(fa[i]==i)cout<<a[i]<<" ";
	}
}

signed main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);

	int t=1;
	//cin>>t;
	while(t--)
	{
		solve();
	}
	
	return 0;
}

I、景区导游

问题描述

屏幕截图 2023-04-08 204407.png

屏幕截图 2023-04-08 204447.png

问题解析

首先,根据题目的解释来看,如果路线是:2->6->5->1,那就相当于求出sum=dis(2,6)+dis(6,5)+dis(5,1)。dis(x,y)表示点x到点y的时间。

至于跳过某点,比如跳过6点,那么就是:sum-dis(2,6)-dis(6,5)+dis(2,5).

这样对于每次跳过某点,我们并不用重新计算花费时间,只要把修改跟跳过的点有关的三条路径长度求出来,再在sum的基础上修改即可。

那么此时问题就变成了,如何快速的求从x点到y点的距离。

注意题目说的,这个景区的路线呈现树的样子。也就是说,这是让我们求在树中任意两点的距离:(样例如图)

未命名绘图5.png

未命名绘图6.png

未命名绘图7.png

由此可知,只要知道了两个点的最近公共祖先,就可以很快的求出在树中任意两点的距离。

现在,要解决的就是快速求出两个点的最近公共祖先了。

关于这个,我这里用的是倍增法求lca,具体的这里就不展开了,感兴趣的同学可以去自行学习一下。

代码

#include<iostream>
using namespace std;
#include<cstdio>
#include<algorithm>
#include<string>
#include<unordered_map>
#include<queue>
#include<vector>
#include<stack>
#include<map>
#include<string.h>
#include<math.h>
#include<iomanip>

#define int long long
#define endl '\n'
typedef long long ll;
typedef pair<ll, ll> PII;
const int N = 1e5 + 50;

int deep[N], fa[N][31], a[N], dis[N];
vector<int>tree[N];
unordered_map<int, unordered_map<int, int>>mymap;

// dfs,用来为 lca 算法做准备。接受两个参数:dfs 起始节点和它的父亲节点。
void dfs(int root, int fno) {
    // 初始化:第 2^0 = 1 个祖先就是它的父亲节点,dep 也比父亲节点多 1。
    dis[root] = dis[fno] + mymap[root][fno];
    fa[root][0] = fno;
    deep[root] = deep[fa[root][0]] + 1;
    // 初始化:其他的祖先节点:第 2^i 的祖先节点是第 2^(i-1) 的祖先节点的第
    // 2^(i-1) 的祖先节点。
    for (int i = 1; i < 31; ++i) {
        fa[root][i] = fa[fa[root][i - 1]][i - 1];
    }
    // 遍历子节点来进行 dfs。
    int sz = tree[root].size();
    for (int i = 0; i < sz; ++i) {
        if (tree[root][i] == fno) continue;
        dfs(tree[root][i], root);
    }
}

// lca。用倍增算法算取 x 和 y 的 lca 节点。
int lca(int x, int y) {
    // 令 y 比 x 深。
    if (deep[x] > deep[y]) swap(x, y);
    // 令 y 和 x 在一个深度。
    int tmp = deep[y] - deep[x], ans = 0;
    for (int j = 0; tmp; ++j, tmp >>= 1)
        if (tmp & 1) y = fa[y][j];
    // 如果这个时候 y = x,那么 x,y 就都是它们自己的祖先。
    if (y == x) return x;
    // 不然的话,找到第一个不是它们祖先的两个点。
    for (int j = 30; j >= 0 && y != x; --j) {
        if (fa[x][j] != fa[y][j]) {
            x = fa[x][j];
            y = fa[y][j];
        }
    }
    // 返回结果。
    return fa[x][0];
}

//求两个点之间的距离
int get_dis(int x, int y)
{
    int z = lca(x, y);
    return dis[x] + dis[y] - dis[z] * 2;
}

/*
6 4
1 2 1
1 3 1
3 4 2
3 5 2
4 6 3
2 6 5 1
*/

void solve()
{
    int n, x, y, m, k, w;
    cin >> n >> k;
    for (int i = 1; i < n; i++)
    {
        cin >> x >> y >> w;
        tree[x].push_back(y);
        tree[y].push_back(x);
        mymap[x][y] = w;
        mymap[y][x] = w;
    }
    dfs(1, 0);
    int sum = 0;
    for (int i = 1; i <= k; i++)
    {
        cin >> a[i];
        if (i > 1)
        {
            sum += get_dis(a[i], a[i - 1]);
        }
    }
    for (int i = 1; i <= k; i++)
    {
        int res = 0;
        if (i == 1)
        {
            res = sum - get_dis(a[i], a[i + 1]);
        }
        else if (i == k)
        {
            res = sum - get_dis(a[i], a[i - 1]);
        }
        else
        {
            res = sum - get_dis(a[i - 1], a[i]) - get_dis(a[i + 1], a[i]) + get_dis(a[i - 1], a[i + 1]);
            
        }
        cout << res << " ";
    }
}

signed main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);

	int t = 1;
	//cin>>t;
	while (t--)
	{
		solve();
	}

	return 0;
}
  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值