本文正在参与「金石计划」

第十四届蓝桥杯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串的熵

问题描绘

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

问题解析

答案: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、锻炼金属

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

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

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

问题解析

能够看出,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、飞机降落

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

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

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

问题解析

一看数据,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、接龙数列

问题描绘

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

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

问题解析

经典的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、子串简写

问题描绘

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

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

问题解析

又是经典的题。

先用一个数组存下来从小到大一切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、整数删去

问题描绘

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

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

问题解析

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

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

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

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

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

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

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

假如某个数被删去了,则在线段树中将对应方位修正成一个很大的值,不要让他影响咱们的判别。

不会线段树的同学也能够看看我主页的文章,有教你怎么把握线段树(基础)。

(做法应该没啥问题,首要忧虑会不会写错代码+常数太大了导致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、景区导游

问题描绘

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

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

问题解析

首要,根据标题的解说来看,假如道路是: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点的间隔。

留意标题说的,这个景区的道路出现树的样子。也便是说,这是让咱们求在树中恣意两点的间隔:(样例如图)

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

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

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

由此可知,只要知道了两个点的最近公共先人,就能够很快的求出在树中恣意两点的间隔。

现在,要处理的便是快速求出两个点的最近公共先人了。

关于这个,我这儿用的是倍增法求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;
}