第十三届蓝桥杯省赛c++b组心得

第一次参加蓝桥杯,复习(yuxi)完期末了,本蒟蒻写一篇博客记录一下

A: 九进制转十进制

答案:  1478

思路:根据进制定义,就是2*9^0 + 2*9^1 + 0*9^2 + 2*9^3 = 1478

B 顺子日期(100%)

答案:    14

思路:   这题题意有争议,我找的是连续的3个上升顺子。这题是蓝桥杯常考的日期算法,枚举从20220101~20221231中合法的日期,然后检查一下日期中是否出现顺子即可。

代码:  

#include <iostream>

using namespace std;

int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int res;

bool cheak(int x)
{
	int year = x / 10000, month = x / 100 % 100, day = x % 100;		       //取出年,月,日,然后判断该日期是否合法
	if(day == 0 || month == 0 || month > 12) return false;
	if(month != 2 && day > days[month]) return false;
	if(month == 2)
	{
		int flag = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);	//闰年时,flag为1,否则为0
		if(day > days[2] + flag) return false;							//闰年时2月有29天,否则就是28天 
	}
	//经过上面代码块后,到这里的日期都是合法的日期,此时判断该日期是否为顺子日期即可
	
	int d[9] = {0};
	for(int i = 1; i <= 8; i ++ )
	{
		d[i] = x % 10;													//倒叙存储到d数组中,则下面找递减的顺子日期 
		x /= 10;
	}
	for(int i = 1; i + 2 <= 8; i ++ )
	{
		if(d[i] - 1 == d[i + 1] && d[i + 1] - 1 == d[i + 2])
		return true;
	} 
	
	return false;														//不是顺子日期,则return false; 
}

int main()
{
	for(int i = 20220101; i <= 20221231; i ++ )
	if(cheak(i)) res ++ ; 
	
	cout << res << endl;
	return 0;
}

C: 刷题统计

 思路:根据数据范围,肯定不能一天一天的加,那么可以将1周的刷题量算出来,为5a+2b,然后分类讨论,当n<5a+2b,则可以模拟一天一天的算。 当n>= 5a+2b,则可以算出总共有多少个星期也就是day = (n / (5a + 2b)) * 7,然后n %= (5a+2b)算出剩余的刷题量。然后将剩余<1周的刷题量再次通过一天一天的模拟获得.

代码如下:

#include <iostream>

using namespace std;

typedef long long ll;

ll a, b, n;
ll day, sum;

int main()
{
    cin >> a >> b >> n;
    ll z = 5 * a + 2 * b;   //z表示一周的刷题量
    
    int cnt = 1;
    if(n < z)           //如果总刷题量<一周的,那么单独一天天的算
    {
        while(sum <= n)
        {
            if(cnt < 5) sum += a;
            else sum += b;
            day ++ ;
            
            cnt ++ ;
            if(cnt == 8) cnt = 1;
        }
    }
    else            //反之,可以通过除法快速获得
    {
        day += (n / z) * 7;
        n %= z;
        
        while(sum < n)  
        {
            if(cnt < 5) sum += a;
            else sum += b;
            day ++ ;
            
            cnt ++ ;
            if(cnt == 8) cnt = 1;
        }
    }
    
    cout << day << endl;
    return 0;
}

D:修剪灌木(100%)

 思路:可以发现,爱丽丝一来一回的剪,那么对于任意一棵灌木爱丽丝要么是去剪它的路上,要么是剪完它了然后继续往下走。那么很明显,对任意灌木,能够生长的最大时间就是爱丽丝剪完它之后的这个过程,这个过程有左边和右边两个选择,所以就是对剪完它之后对左边和右边取最大

代码如下:

#include <iostream>

using namespace std;

const int N = 20000;

int n, f[N];

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++ )
    f[i] = max(2 * (n - i), 2 * (i - 1));
    
    for(int i = 1; i <= n; i ++ )
    cout << f[i] << endl;
    return 0;
}

E:X 进制减法

这题没有读懂.......略过

F: 统计子矩阵

思路:在做这题时感觉时间不太够,所以写了一个暴力就看下面了,下来听了聚聚们说是双指针,暴力思路就是二位前缀和处理一下,然后枚举左上角的端点,然后枚举右下角的端点。这样就确定了一个矩形,然后通过二位前缀和O(1)的算出矩阵中所有数字之和然后判断是否大于K即可,时间复杂度为O(n^4),可以过一半以上的数据。

双指针思路:将所有矩阵化为1维(用列的前缀和数组pre来处理),相当于只有了一个数组,然后枚举上界线和下界线,再用双指针l和r来枚举列,在枚举r的过程中,如果大于K了,那么此时r再往右移是没有意义的,此时只需要l右移。那么这样列的枚举是O(n)的,那么时间复杂度就是O(n^3)

代码如下:

#include <iostream>

using namespace std;

const int N = 550;

typedef long long ll;

int n, m, S;
ll pre[N][N];

int main()
{
	cin >> n >> m >> S;
	for(int i = 1; i <= n; i ++ )
	for(int j = 1; j <= m; j ++ )
	{
		cin >> pre[i][j];
		pre[i][j] += pre[i - 1][j];
	}
	
	ll res = 0;
	for(int i = 1; i <= n; i ++ )       //枚举上边界
	for(int j = i; j <= n; j ++ )       //枚举下边界
	{
		ll sum = 0;
		for(int l = 1, r = 1; r <= m; r ++ )//双指针扫描
		{
			sum += s[j][r] - s[i - 1][r];
			while(sum > S)                  //若此时sun>S,则l右移,且把l那一列的值减去
			sum -= s[j][l] - s[i - 1][l], l ++ ;
			
			res += r - l + 1;        //每次r右移,都会有新的一部分块加入矩阵,用这个块和其他块组合得到的也是
		}                            //新的子矩阵。而用不是新的子矩阵来组合则会重复
	}                                //所以每次加入的新的子矩阵就是r - l + 1
	
	cout << res << endl;
	return 0;
}

G: 积木画

 思路:这题是简化版的蒙德里安的梦想,可以用状压dp,也可以采用递推的思路,这里讲述递推的做法,通过最后一步完整的拼成一个矩形来划分。

为什么可以这样划分?因为放三角后你只能放横着的矩形,如果你放竖着的中间就会有一个空着的就会不合法。所以三角和矩形的组合只能这样组合。这样的划分就可以将要求的原问题划分成一个个不重复(因为最后一步的处理不同,是不重复的)的子问题,这些子问题之和就是答案。既定义f[n]为铺满n块的方案种数,所以f[n] = f[n - 1] + f[n - 2] + 2*f[n - 3] + 2 * f[n - 4]…………(因为三角形可以倒着放,所以有三角形的划分需要乘以2),可以得到f[n] = f[n - 1] + f[n - 2] + pre;pre就是方案种数的前缀和,f数组忘记开LL了,卒

递推代码如下:

#include <iostream>

using namespace std;

typedef long long LL;

const LL N = 1e7 + 10, mod = 1000000007;

LL n, f[N], pre;

int main()
{
    cin >> n;
    f[0] = 1, f[1] = 1, f[2] = 2;
    for(int i = 3; i <= n; i ++ )
    {
        pre += 2 * f[i - 3], pre %= mod;
        f[i] = f[i - 1] + f[i - 2] + pre, f[i] %= mod;
    }
    
    cout << f[n] << endl;
    return 0;
}

状态压缩dp代码如下:

#include <iostream>
#include <cstring>

using namespace std;

const int N = 1e7 + 10, mod = 1000000007;

int n, f[2][4];
int g[4][4] = {
    {1, 1, 1, 1},
    {0, 0, 1, 1},
    {0, 1, 0, 1},
    {1, 0, 0, 0},
};

void init(int x)
{
    for(int j = 0; j < 4; j ++ )
    f[x & 1][j] = 0;
}

int main()
{
    cin >> n;
    
    f[1][0] = 1;
    for(int i = 1; i <= n; i ++ )
    {
        init(i + 1);                   
        for(int j = 0; j < 4; j ++ )    
        for(int k = 0; k < 4; k ++ )
        f[(i + 1) & 1][k] = (f[(i + 1) & 1][k] + f[i & 1][j] * g[j][k]) % mod;
    }
    
    cout << f[n + 1 & 1][0] << endl;
    return 0;
}

H: 扫雷

思路:这题暴力骗分了,思路就是枚举排雷火箭,看看它能引爆哪些炸雷,然后dfs去将这一个连通块用vis全部标记,最后再去统计看看有多少地雷被引爆。能否引爆就根据圆心距和半径和的关系来判断(高中数学??...)

代码如下:

#include <iostream>
#include <cmath> 

using namespace std;

const int N = 1e6;

struct e
{
	double x, y, r;
}k[N+N];

int n, m;
bool st[N + N];

void dfs(int u)
{
	for(int i = 1; i <= n + m; i ++ )
	{
		if(!st[i] && u != i)			//枚举所有没有爆炸过的地雷和火箭
		{
			double d = sqrt((k[i].x - k[u].x) * (k[i].x - k[u].x) + (k[i].y - k[u].y) * (k[i].y - k[u].y));  //圆心距 
			if(d <= k[u].r)		//圆心距 == r1 + r2,则相切, 圆心距 < r1 + r2,则相交。满足条件则代表可以引爆
			{
				st[i] = true;				//标记i被引爆了 
				dfs(i);						//被引爆的继续枚举,看看它可以连锁反应哪些 
			} 
		} 
	}
}

int main()
{
	cin >> n >> m;
	for(int i = 1; i <= n; i ++ ) cin >> k[i].x >> k[i].y >> k[i].r;				//输入地雷 
	for(int i = n + 1; i <= n + m; i ++ ) cin >> k[i].x >> k[i].y >> k[i].r;		//输入引爆火箭 
	
	for(int i = n + 1; i <= n + m; i ++ )
	{
		st[i] = true;									//搜索所有火箭,将这个火箭的连通块枚举出来(相互引爆的必然是一个连通块) 
		dfs(i);
	}
	
	int res = 0;
	for(int i = 1; i <= n; i ++ )						//枚举炸弹,看看哪些炸弹被引爆了,不要枚举火箭
	if(st[i])
	res ++ ; 
	
	cout << res << endl;
	return 0;
}

 在代码源上过了40的数据

I: 李白打酒加强版

思路:本蒟蒻又又又暴力了QAQ,不过下来看了dp的思路。这里暴力就不说了就是一个dfs,这里说说dp的做法;dp的重点是描述状态,怎么才能把本题的状态描述清楚呢? 定义f[i][j][k][0]代表遇花i次,遇店j次,壶中有酒k,当前在花,f[i][j][k][1]表示…………当前在店。

所以f[i][j][k][0] += (f[i - 1][j][k + 1][0] + f[i - 1][j][k + 1][1]);

同理f[i][j][k][1] += (f[i][j - 1][k / 2][0] + f[i][j - 1][k / 2][1]);所以得代码:

#include <iostream>

using namespace std;

const int N = 110, MOD = 1000000007;

int f[N][N][N][2], n, m;

int main()
{
    cin >> m >> n;
    
    f[0][0][2][0] = 1;
    
    for(int i = 0; i <= n; i ++ )
    for(int j = 0; j <= m; j ++ )
    for(int k = 0; k <= n + 2; k ++ )
    {
        if(i == 0 && j == 0) continue;
        int &v = f[i][j][k][0], &u = f[i][j][k][1];
        
        if(i - 1 >= 0) 
        v = (v + f[i - 1][j][k + 1][0]) % MOD, v = (v + f[i - 1][j][k + 1][1]) % MOD;
        
        if(j - 1 >= 0 && k % 2 == 0)
        u = (u + f[i][j - 1][k / 2][0]) % MOD, u = (u + f[i][j - 1][k / 2][1]) % MOD;
    }
    
    cout << f[n][m][0][0] << endl;
    return 0;
}

 J: 砍竹子

蒟蒻不会,23333

总结:感觉这次省赛难度还好,但是还有点粗心,拼积木那题忘记开LL,民间数据只过了4个点,得了一个省一。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一万次悲伤_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值