第十二届蓝桥杯省赛第一场C++A/B/C组真题

1. 砝码称重

一个有限制的背包问题。

 状态标识:   f[i][j] 表示:考虑前i件砝码,重量为j的情况能否构造。

      1.1 时间复杂度分析

这样dp的好处是,我们通过不同的构造方式能构造出相同的重量,试想暴力的思路:经典的生成子集问题,每件砝码都有选或不选两种情况,如此排列组合,最终构造方式是2^n - 1 (除去了空集),把所有状态都尝试遍历一并啊,一定会超时。2^32 - 1就是int的最大值。而1秒内,能过的c/c++程序在1e7~1e8间

而当状态表示为某重量是否可以构造出来。那所需要考虑的范围就处于1~sum间(能够造出来的重量一定小于总和)。而题目指出sum <= 1e5。

      1.2 思路

设a[i] 为 第 i 件砝码的重量。设放在左边是加上砝码重量,放在右边是减去砝码重量,也就是说,所谓天平,其实就是每件砝码可以被加或者被减两种状态。

重要的事再说一遍,f[i][j] 表示:考虑前i件砝码,重量为j的情况能否构造。那么考虑前i件,j能不能构造出来,就要看考虑前i-1件的情况了:

  • 第i件物品不使用:若考虑前i-1件,重量为j能构造出来,那么我考虑前i件,第i件砝码那不使用,j就能构造出来。
  • 第i件物品使用:
    • 若考虑前i-1件,重量j - a[i]能构造出来(j >= a[i])那么只要加上第i件物品的重量a[i],j就可以被构造出来。
    •  若考虑前i-1件,重量j + a[i]能构造出来,那么只要减去第i件物品的重量a[i],j就可以被构造出来。
    • 若考虑前i-1件,重量a[i] - j 能构造出来(j <= a[i])那么只要减去第i件物品的重量a[i],j就可以被构造出来。构造出-j,事实上得到的重量也是j。

若满足以上状态,则使f[i][j] = 1,即考虑前i件砝码,重量为j的情况能被构造出来。

dp初状态:f[0][0] = 1。

虽然我们答案重量0是不会被算进去的,但是当我们加上第a[i]件物品时,为了方便计算,就让f[0][0] = 1,那么算f[1][a[1]]的时候,当j = a[1]的时候,f[1][j - a[1]]能被构造出来。即让f[][a[i]] = 1。

      1.3 代码:

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i = a;i <= b;++i)
#define per(i,a,b) for(int i = b;i <= a;--i)
using namespace std;

const int N = 100 + 10;
int n,sum;
int a[N];
int f[N][100010];

int main()
{
	cin >> n;
	rep(i,1,n) scanf("%d",&a[i]),sum+=a[i];

	f[0][0] = 1;
	rep(i,1,n){
		rep(j,0,sum){
			if(f[i-1][j] or f[i-1][j+a[i]] or (j >= a[i] and f[i-1][j-a[i]])  or ((j <= a[i] and f[i-1][a[i]-j]))){
				f[i][j] = 1;
			}

		}
	}
	int ans = 0;
	
	rep(i,1,sum) ans += f[n][i];
	cout << ans << endl;
    return 0;
}


2. 时间显示

这题没啥好讲的,为了照顾萌新,讲下题外的基础知识,他要是要让你输入年月日,如"2001-09-14",我们可以使用scanf("%lld-%lld-%lld",&year,&month,&day); 如果换成了其他字符,也可以在scanf("");中把那个分割符换成相应的就行。

本题范围是1e18, 注意我是直接用int 定义的变量,为了方便,我在宏定义除用int代替了long long,所以其实也是没啥问题的,只需要注意输出和输入要lld即可。注意int main(),要改成signed。有人会问会不会爆空间?我回答是,会有可能的,在打cf的时候我确实因为这个爆空间,甚至时间都暴过,但是这是极少的,而且对于蓝桥杯而言,大家犯错的更可能是忘记使用long long。

贴代码:

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = a; i <= b; ++ i)
#define int long long 
using namespace std;



signed main()
{
	int x; cin >> x;
	x/=1000;
	x %= (3600 * 24);
	int hh = x / 3600; x %= 3600;
	int mm = x / 60; x %= 60;
	printf("%02lld:%02lld:%02lld",hh,mm,x);
	
    return 0;
}


3. 杨辉三角型

对于这种题目,如果是B组,我推荐最好用暴力,如果每题都暴力骗分的话,省一还是很容易的(B组),很多时候,若用更好的方法去解决,可能一分得不到。毕竟蓝桥杯这个比赛得奖很水,很多人参加这个比赛都是为了混一个国奖,但是蓝桥杯的题,负责任的说,一点也不水。

为了求稳,就推荐用最简单,最暴力的方式,耐心的敲完每一题。每一题混一点分,这样省一还是很稳的。(B组国赛也是如此,当然这种比赛方式比较适合那种有耐心的,善于模拟的人,就是不能暴力,也要模拟出一种暴力方式,强行暴力骗分。)

最暴力的解题方式相信大家在上C语言课的时候,都应该接触过,这里就讲满分思路。

      3.1 思路

这是一个思维题,主要要会找规律。杨辉三角是对称的,第一次出现一定是在左边。因此不考虑右边。C(i,j)的意思是,横着看的第i行,斜着看的第j列,(从0开始。)的值为C(i,j),C(i,j)是值组合数

C(a, b) = a!/b!(a-b)! = a * (a-1) .. / b!

组合数的值刚好等于杨辉三角横着看的第i行,斜着看的第j列处的值。

至于这个数,以横着看每行,竖着看每列的方式 在杨辉三角中第几个数是:

Num(r,k) = (r+1)*r/2 + k+1

前面一段是第r-1行前的个数,刚好是等差数列前n项和,k+1,是该数在r的第几列。

所有的数            1  ---> C(0, 0)
          1 
        1   2  ---> C(2, 1)
      1   3                             ---> C(2n, n)
    1   4   6  ---> C(4, 2)
  1   5   10
1   6   15  20 ---> C(6, 3)

我们只要找到,C(r,k)==n,最后答案就是Num(r,k)。那怎么找呢?从哪开始找呢?

有两种方式,1. 遍历每行,将此行逐列遍历,找不到,就继续在下一行中找每一列。2. 遍历每列,将此列中每行逐列。

我们拿遍历每列讲(此列是斜着看),如我们要遍历第1列(从0开始),第一列的元素有:

2、3、4、5、6在这里面找n,注意到每一行里的数据都是递增的,因此我们很自然可以想到用二分来找数。不过我们真正查找的时候,不是直接就有2、3、4、5这些数的,我们是通过C(r,k)组合数公式得到的,如2 = C(2,1) 6 = C(6,1)。因此我们二分的是行区间。

 行区间的开始是多少呢?一定是C(2*k,k),斜着看的第一行刚好都是C(k*2,k)。

而最大值是:max(n,l),为什么不是n呢?因为n = 1时,l = 2,我们要保证l <= r。

行区间得到了,那列最大能取多少呢?

 n最大1e9,C(34, 17) > 1e9, C(32, 16) < 1e9,因此只要枚举前16个斜行即可!

      3.2 代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#define ll long long

using namespace std;

int n;

ll C(int a, int b){
    ll res = 1;
    for(int i = a, j = 1; j <= b; i--, j++){
        res = res * i / j;
        if(res > n) return res;
    }
    return res;
}

bool num(int u){
    ll l = u * 2, r = max((ll)n, l);
    while(l < r){
        int mid = l + r >> 1;
        if(C(mid,u) >= n) r = mid;
        else l = mid + 1;
    }
    
    if(C(r,u) != n) return false;
    
    cout << r * (r + 1) / 2 + u + 1 << endl;
    
    return true;
}

int main()
{
    cin >> n;
    for(int i = 16; ; i--){
        if(num(i)) break;
    }
    return 0;
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值