第十三届蓝桥杯大赛软件赛国赛B组C/C++(个人题解)


分享一下赛中的想法和一部分的思路,没有验证对不对,有大佬看到错误滴滴我~~
国一不就有手就行


试题 A: 2022(DP)

os:202210应该要跑很久吧,那可怎么办呀,不回来了呀。哦,好像可以DP~

思路
定义 dpi,x,y 表示第 i 个数等于 x 且前 i 个数和为 y 的方案数。
状态转移参看代码,时间复杂度 O( n4 ),得跑几秒,多等等~
记得开 ll

#include<bits/stdc++.h>
#define ll long long
using namespace std;

ll dp[15][2500][2500];

int main(){

	for(int i = 1; i <= 2022; i++) dp[1][i][i] = 1;
		
	for(int i = 2; i <= 10; i++){
		for(int j = 1; j<= 2022; j++){
			for(int z = j; z <= 2022; z++){
				for(int k = j+1; k+z <= 2022; k++){
					dp[i][k][k+z] += dp[i-1][j][z];
				}
			}
		}
	}
	
	ll sum = 0;
	for(int i = 0; i <= 2022; i++) sum += dp[10][i][2022];
	
	cout << sum << endl;
			
    return 0;
}

// 379187662194355221

试题 B: 钟表(模拟)

os:00:00:00不就是答案么,垃圾题。
正在阅读理解的我,看到了公告:00:00:00除外
os:*****

思路
一个小时是 30°,一分钟是 6°,一秒是 6°

#include<bits/stdc++.h>
#define ll long long
using namespace std;

int main(){

	int res = 0;
	for(int i = 0; i <= 6; i++){
		for(int j = 0; j < 60; j++){
			for(int z = 0; z < 60; z++){
				double s = 30.0 * (i+(j*60.0+z)/3600.0);
				double f = 6.0 * (j+z/60.0);
				double m = z * 6.0;
				double a = fabs(f - s); if(a > 180) a = 360 - a;
				double b = fabs(f - m); if(b > 180) b = 360 - b;
				if(a == 2 * b){
					cout << i << " " << j << " " << z << " " << a << " " << b << endl;
					res++;
				}
			}
		}
	}
	
	cout << res << endl;
			
    return 0;
}

// 4 48 0

试题 C: 卡牌(二分check)

os:嗯~,二分大水题

思路
二分能凑出的套牌数,二分check即可~~
时间复杂度 O( n*log(1e18) )

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int maxn = 2e5 + 5;
ll a[maxn], b[maxn];
ll n, m;

bool check(ll cnt){
	ll sum = m;
	for(int i = 1; i <= n; i++){
		if(a[i] >= cnt) continue;
		ll num = cnt - a[i];
		if(num <= b[i] && num <= sum){
			sum -= num;
			continue;
		}
		return 0;
	}
	return 1;
}

int main(){

	scanf("%lld %lld", &n, &m);
	for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
	for(int i = 1; i <= n; i++) scanf("%lld", &b[i]);
			
	ll res = 0;
	ll l = 0, r = 1e18;
	while(l <= r){
		ll mid = l + r >> 1;
		if(check(mid)){
			res = mid;
			l = mid + 1;
		}
		else r = mid - 1;
	}
	
	cout << res << endl;
			
    return 0;
}

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

3
*/

试题 D: 最大数字(dfs)

os:能加一定要加,得判断能不能减。不会是数位DP吧,***,算了,暴搜吧

思路
由题得,优先给高位变成大于原来的数。最好变成 9,但可能存在加操作不够的情况,尽量大即可。显然,也可以通过减操作变成 9

综上,对于每一个数位都有两种选择,是加到 9 还是减到 9

直接 dfs 暴力枚举每一种情况,加一个记忆化标记一下不重复搜索。时间复杂度 O(2|n|)|n| 表示 n 的位数。

#include<bits/stdc++.h>
#define ll long long
using namespace std;

bool v[100][105][105]; 
ll c[100];
ll res, p[100];
void dfs(ll now, int pos, ll a, ll b){ // a+ b-
	if(v[pos][a][b]) return;
	v[pos][a][b] = 1;
	if(a == 0){
		for(int i = pos; i >= 1; i--){
			if(c[i]+1 <= b){
				now = now + (9-c[i]) * p[i-1];
				b -= c[i] + 1;
			}
		}
		res = max(res, now);
		return;
	}
	res = max(res, now);
	if(pos == 0) return;
	
	int tmp = min((9-c[pos]), a);
	dfs(now + tmp * p[pos-1], pos-1, a-tmp, b);
	
	if(c[pos]+1 <= b) dfs(now + (9-c[pos]) * p[pos-1], pos-1, a, b-c[pos]-1);
} 

int main(){

	ll n, a, b;
	scanf("%lld %lld %lld", &n, &a, &b);
	
	p[0] = 1;
	for(int i = 1; i <= 20; i++) p[i] = p[i-1] * 10;
	
	res = n;
	int m = 0;
	while(n){
		c[++m] = n % 10;
		n /= 10;
	}
	
	dfs(res, m, a, b);
	
	cout << res << endl;
			
    return 0;
}

/*
123 1 2

933
*/

试题 E: 出差(dij)

os:嗯~,最短路裸题,你就拿这个考验选手,那个选手经不起这样的考验

思路
把点权当边权跑 dij 即可,res = dis[n] - c[n]

#include<bits/stdc++.h>
#define ll long long
using namespace std;

vector<pair<int, ll> > ma[1005];
ll c[1005];

typedef struct Node{
	int to;
	ll w;
} node;

bool operator < (node A, node B){
	return A.w > B.w;
}

priority_queue<node> qu;
ll dis[1005];

int main(){

	int n, m;
	scanf("%d %d", &n, &m);
	for(int i = 1; i <= n; i++) scanf("%lld", &c[i]);
	for(int i = 1; i <= m; i++){
		int u, v, w;
		scanf("%d %d %d", &u, &v, &w);
		ma[u].push_back(make_pair(v, w));
		ma[v].push_back(make_pair(u, w));
	}
	
	for(int i = 1; i <= n; i++) dis[i] = 2e18;
	dis[1] = 0;
	qu.push({1, 0});
	while(!qu.empty()){
		int now = qu.top().to;
		qu.pop();
		for(auto i : ma[now]){
			int to = i.first;
			ll nval = dis[now] + i.second + c[to];
			if(nval < dis[to]){
				dis[to] = nval;
				qu.push({to, nval});
			}
		}
	}
	
	cout << dis[n]-c[n] << endl;
	
    return 0;
}

/*
4 4
5 7 3 4
1 2 4
1 3 5
2 4 3
3 4 5

13
*/

试题 F: 费用报销(dp)

os:这搞毛呀,总不能枚举吧。先塞进去365张口头支票~

思路
观察数据,总金额不能超过 5000,一年也就 365 天。故而 365 * 5000 的 dp 妥妥可以过。
首先,这些支票的时间不是连续的,先塞进去 365+k 张支票(金额为零)使得时间连续。
定义:dpx,y 表示前 x 天是否可以得到金额为 y 的方案。
状态转移方程参看代码~

#include<bits/stdc++.h>
#define ll long long
using namespace std;

typedef struct Node{
	int id;
	ll w;
} node;

node v[2005];

bool cmp(node A, node B){
	if(A.id == B.id) return A.w < B.w;
	return A.id < B.id;
}

int M[20] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int f(int a, int b){
	int res = b;
	for(int i = 1; i < a; i++) res += M[i];
	return res;
}

ll dp[400][5005];

int main(){
	
	int n, m, k;
	cin >> n >> m >> k;
	for(int i = 1; i <= n; i++){
		int a, b, c;
		cin >> a >> b >> c;
		v[i].id = f(a, b);
		v[i].w = c;
	}
	for(int i = 1; i <= 365+k; i++){
		v[n+i].id = i;
		v[n+i].w = 0;
	}
	n += 365+k;	
	
	v[0].id = 0;
	sort(v+1, v+1+n, cmp);
	
	for(int i = 1; i <= n; i++){
		if(v[i].id != v[i-1].id && v[i].id-k > 0){ // 新的一天,把前k天的状态转移过来
			for(int j = 1; j <= m; j++) 
				dp[v[i].id-k][j] |= dp[v[i].id-k-1][j];
		}
		if(v[i].w == 0) continue;
		dp[v[i].id][v[i].w] |= 1;
		if(v[i].id <= k) continue;
		for(int j = 0; j <= m; j++){
			if(dp[v[i].id-k][j] && j + v[i].w <= m){
				dp[v[i].id][j + v[i].w] |= 1;
			}
		}
	}
	
	int res = 0;
	for(int i = 0; i <= m; i++) if(dp[365][i]) res = i;
	cout << res << endl;
	
    return 0;
}

/*
4 16 3
1 1 1
1 3 2
1 4 4
1 6 8

10
*/

试题 G: 故障(概率题)

os:这啥呀,概率题我咋能会,都是数论队友的题,猜结论吧

思路
就是硬猜的结论,看代码吧,一看知道咋算了

#include<bits/stdc++.h>
#define ll long long
using namespace std;

ll p[100];
ll v[100][100];
ll a[100], c[100], d[100];

typedef struct Node{
	int id;
	double w;
} node;

node res[2005];

bool cmp(node A, node B){
	if(fabs(A.w - B.w) < 1e-3) return A.id < B.id;
	return A.w > B.w;
}

int main(){
	
	int n, m;
	cin >> n >> m;
	for(int i = 1; i <= n; i++) cin >> p[i];
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= m; j++){
			cin >> v[i][j];
			if(v[i][j] != 0) a[i] |= (1<<(j-1));
		}
	}
	int k, flag = 0;
	cin >> k;
	for(int i = 1; i <= k; i++) cin >> c[i], d[c[i]] = 1, flag |= (1<<(c[i]-1));
	double sum = 0;
	for(int i = 1; i <= n; i++){
		res[i].w = p[i];
		res[i].id = i;
		for(int j = 1; j <= m; j++){
			if(d[j] == 1) res[i].w *= 1.0 * v[i][j];
			else res[i].w *= 1.0 * (100-v[i][j]);
		}
		sum += res[i].w;
	}
	for(int i = 1; i <= n; i++) res[i].w = res[i].w / sum*100;
	
	sort(res+1, res+1+n, cmp);
	
	for(int i = 1; i <= n; i++) printf("%d %.2lf\n", res[i].id, res[i].w);
	
    return 0;
}

/*
3 5
30 20 50
0 50 33 25 0
30 0 35 0 0
0 0 0 25 60
1
3

2 56.89
1 43.11
3 0.00
*/

试题 H: 机房(lca)

os:树剖?emmm,lca,你就拿这个考验选手,那个选手经不起这样的考验

思路
dfs处理一下各个点的祖先和从根节点到 u 结点的点权和。
u != v时:resu,v = sum[u] + sum[v] - sum[fa] * 2 + w[fa]
u == v时:resu,v = sum[u] - sum[fa]

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int maxn = 1e5;
vector<int> ma[maxn];
int w[maxn];
int f[maxn][21];
int dep[maxn];

void dfs(int u, int fa){
	w[u] = w[fa] + ma[u].size();
	dep[u] = dep[fa] + 1;
	f[u][0] = fa;
	for(int i = 1; i < 21; i++) f[u][i] = f[f[u][i-1]][i-1];
	for(auto v : ma[u]){
		if(v == fa) continue;
		dfs(v, u);
	}
}

int lca(int x, int y){
	if(dep[x] < dep[y]) swap(x, y);
	if(x == y) return f[y][0];
	for(int i = 20; i >= 0; i--){
		if(dep[f[x][i]] >= dep[y]) x = f[x][i];
	}
	for(int i = 20; i >= 0; i--){
		if(f[x][i] != f[y][i]){
			x = f[x][i];
			y = f[y][i];
		}
	}
	return f[y][0];
}

int main(){
	
	
	int n, m;
	cin >> n >> m;
	for(int i = 2; i <= n; i++){
		int x, y;
		cin >> x >> y;
		ma[x].push_back(y);
		ma[y].push_back(x);
	}
	
	dfs(1, 0);
	
	for(int i = 1; i <= m; i++){
		int x, y;
		cin >> x >> y;
		int fa = lca(x, y);
		if(x != y) cout << w[x] + w[y] - 2 * w[fa] + ma[fa].size() << endl;
		else cout << w[x] - w[fa] << endl;
	}
	
    return 0;
}

/*
4 3
1 2
1 3
2 4
2 3
3 4
3 3

5
6
1
*/

试题 I: 齿轮(数学题and调和级数)

os:擦,这不是高中的题,不记得了

思路
通过角速度和线速度的关系推到,可以发现,边上两个轮的半径比就是前后角速度的比。
多次查询,肯定要预处理。
如何能快速的处理出来任意两个轮子的半径比。
我当时用的是 n * n0.5 的写法。但是可以在调和级数(n * log)的时间复杂度内解决。
n * n0.5 的写法:

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int maxn = 2e6 + 5;
int r[maxn], v[maxn], res[maxn];

int main(){
	
	int n, q;
	scanf("%d %d", &n, &q);
	for(int i = 1; i <= n; i++) scanf("%d", &r[i]), v[r[i]]++;
	
	for(int i = 1; i <= n; i++){
		for(int j = 2; j * j <= r[i]; j++){
			if(r[i] % j == 0){
				if(v[j]) res[r[i]/j] = 1;
				if(v[r[i]/j]) res[j] = 1;
			}
		}
		if(v[r[i]] > 1) res[1] = 1;
	}
	if(v[1]){
		for(int i = 1; i <= n; i++) res[r[i]] = 1;
	}
	
	while(q--){
		int x;
		cin >> x;
		if(x > 2e5 || res[x] == 0) cout << "NO" << endl;
		else cout << "YES" << endl;
	}
	
    return 0;
}

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

YES
YES
NO
*/

调和级数的写法

	for(int i = 1; i <= n; i++){
		if(v[i] == 0) continue;
		for(int j = 2*i; j <= n; j += i){
			if(v[j]){
				res[j/i] = 1;
			}
		}
		if(v[i] > 1) res[1] = 1;
	}

试题 J: 搬砖(dp)

os:好难呀

思路
观察数据,会发现,这可能是一个大水题。
砖的重量不大,并且一个砖上放的砖头的重量和要小于这个砖。所以01背包即可。

ps:当时为了稳妥偏分,对于n比较小的情况作了二进制枚举,然后对于 n 比较大的情况写了dp。

#include<bits/stdc++.h>
#define ll long long
using namespace std;

typedef struct Node{
	int v;
	int w;
} node;

node v[2005];

bool cmp(node A, node B){
	if(A.w == B.w) return A.v > B.v;
	return A.w < B.w;
}

int dp[2005*20];

int main(){
	
	int n;
	cin >> n;
	for(int i = 1; i <= n; i++) cin >> v[i].w >> v[i].v;

	sort(v+1, v+1+n, cmp);
	
	if(n <= 20){
		int res = 0;
		for(int k = 1; k <= (1<<n); k++){
			int sum = 0, val = 0;
			for(int i = 0; i < n; i++){
				if((k>>i) & 1){
					if(v[i].w >= sum){
						sum += v[i].w;
						val += v[i].v;
					}
					else break;
				}
			}
			res = max(res, sum);
		}
		cout << res << endl;
	}
	else{
		for(int i = 1; i <= n; i++){
			for(int j = v[i].w; j >= 0; j--){
				dp[j+v[i].w] = max(dp[j+v[i].w], dp[j] + v[i].v);
			}
		}
		int res = 0;
		for(int i = 1; i <= n*20; i++) res = max(res, dp[i]);
		cout << res << endl;
	}

    return 0;
}

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

10
*/
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值