1. 砝码称重
一个有限制的背包问题。
状态标识: 表示:考虑前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 件砝码的重量。设放在左边是加上砝码重量,放在右边是减去砝码重量,也就是说,所谓天平,其实就是每件砝码可以被加或者被减两种状态。
重要的事再说一遍, 表示:考虑前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)是值组合数:
组合数的值刚好等于杨辉三角横着看的第i行,斜着看的第j列处的值。
至于这个数,以横着看每行,竖着看每列的方式 在杨辉三角中第几个数是:
前面一段是第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;
}