第十三届蓝桥杯C++B组题解

感想

比赛完了,感想就是题目质量出得真得是很一般,很一般,原因应该是没有验题叭。导致第二题题目意思理解很模糊,连续的顺子歧义太大,第五题考试的时候没有看懂,X进制和十进制之间的转换又是什么呢?哎,希望能拿到省一叭艹。
今天在acwing上面交了一下搭积木,递推公式一步写错了,结果直接寄了,绝了,不说了,太傻逼了(第一次公开场合骂人额鹅鹅鹅)
考试的时候,不停地给我跳黄牌,心烦意乱的,状态确实没有发挥到最好,可惜了。

申明:本题解在ACWing上已经AC

A 九进制转十进制

这道题就是一个模拟就可以了,答案是1478

#include 
#include 
#include 

using namespace std;

int main()
{
    int n = 2022;
    int res = 0, t = 1;
    while (n)
    {
        res += t * (n % 10);
        t *= 9;
        n /= 10;
    }
    cout << res << endl;
    return 0;
}

B 顺子日期

这题就很奇怪了,考试的时候没怎么仔细想,之后才发现,问题很大,组委会必须背锅。0算不算连续的数字呢?

考试的时候我是没有仔细去想,直接编程写的太麻烦惹,算出来是14,一月份20~29总共是十天,十二月30和31日两天,10月12日,11月23日,总共14天

C 刷题统计

简单的模拟,这道题就是先把日期给除到每周,看还剩下几天,分成前5天和后两天比较,时间复杂度O(1)

#include 
#include 
#include 

using namespace std;
const int N = 100010;
typedef long long ll;

ll a, b, n;

int main()
{
    cin >> a >> b >> n;

    ll res = 0;
    ll cnt = n / (5 * a + 2 * b);
    res += cnt * 7;
    n %= 5 * a + 2 * b;

    if (n <= 5 * a) 
    {
        res += n / a;
        n %= a;

        if (n) res ++;  
    }
    else
    {
        res += 5;
        n -= 5 * a;

        if (n <= b) res ++ ;
        else res += 2;
    }

    cout << res << endl;
    return 0;
}

D 修剪灌木

这道题是一道思维题,就是说灌木在第一次被剪到下一次被剪的时间段应该是长得最高,同时两个方向取最大值即可,时间复杂度:O(n)

#include 
#include 
#include 

using namespace std;
const int N = 10010;
typedef long long ll;

int n;
int a[N];

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ )
    {
        int t = max(2 * (i - 1), 2 * (n - i));
        printf("%d\n", t);
    }

    return 0;
}

E X进制减法

这道题也是一道思维题。两个数尽量差最小,不就是每一位进制尽可能小嘛,这题进制好像都无所谓,那每一位最小进制是多少呢,比较适合连个数中最大的数加1嘛。
考试的时候,不知道X进制跟10进制怎么转换的,想了一会,寄了

#include 
#include 
#include 

using namespace std;
const int N = 100010, MOD = 1000000007;
typedef long long ll;

int n, m1, m2;
int a[N], b[N], c[N];

int main()
{
    cin >> n;
    cin >> m1;
    for (int i = m1 - 1; i >= 0; i -- ) scanf("%d", &a[i]);
    cin >> m2;
    for (int i = m2 - 1; i >= 0; i -- ) scanf("%d", &b[i]);
    
    ll res = 0, m = max(m1, m2);
    for (int i = m - 1; i >= 0; i -- )
    {
        c[i] = max(2, max(a[i], b[i]) + 1);
    }

    for (int i = m - 1; i >= 0; i -- )
    {
        if (i)
            res = (res + (ll)(a[i] - b[i])) * c[i - 1] % MOD;
        else
            res = (res + a[i] - b[i]) % MOD;
    }
    cout << res << endl;       
    
    return 0;
}

F 统计子矩阵

这道题用到算法了,是前缀和+双指针,如果学过的话比较好理解的,当然暴力应该是可以做到60%的数据,不过这道题不是新出的题目惹

#include 
#include 
#include 

using namespace std;

typedef long long LL;
const int N = 510;

int n, m, T;
int s[N][N];

int main()
{
    scanf("%d%d%d", &n, &m, &T);
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
        {
            scanf("%d", &s[i][j]);
            s[i][j] += s[i - 1][j];
        }

    LL res = 0;
    for (int i = 1; i <= n; i ++ )
        for (int j = i; j <= n; j ++ )
            for (int l = 1, r = 1, sum = 0; r <= m; r ++ )
            {
                sum += s[j][r] - s[i - 1][r];
                while (sum > T)
                {
                    sum -= s[j][l] - s[i - 1][l];
                    l ++ ;
                }
                res += r - l + 1;
            }

    printf("%lld\n", res);
    return 0;
}

G 积木画

这道题是一道递推。我的想法可能比较奇怪,就是考虑最后一列状态作为分界点:分为最后一列摆0个,只摆上面一行,只摆下面一行和最后一列都摆。考试的时候也只想到了这个方法,因为这道题行数只有2维,不需要状态压缩。
递推公式该怎么考虑呢?
我是用0表示这一列不摆,1表示这一列摆满,2表示只摆上面,3表示只摆下面。
首先是这一列不摆,那上一列一定要摆满,否则会有空隙f[i][0]=f[i-1][1],接着是摆满,摆满可以上一列刚好摆满f[i-1][1],也可以上一列不摆,摆两个横的f[i-1][0],也可以是上一列摆第一行,这一列摆一个三角形,同理对称一下f[i-1][2]+f[i-1][3],综上就是f[i][1]=f[i-1][0]+f[i-1][1]+f[i-1][2]+f[i-1][3]。接着是这一列只摆上面一行,那么可以是什么呢?这一列上面一行可以是一个横着的12积木,也可以是一个三角形凸出来,如果摆12的积木,那么前一列必须把下面补齐也就是f[i-1][3]表示上一列只摆下面一行。最后就是这一列只摆下面一行,同这一列摆第一行。
初始化的时候,因为我这个状态比较特殊,所以第一列我直接自己赋值了,因为第一列不能只摆一行,所以循环最好从第二列开始。
综上,就是大概四个状态递推就可以了。因为数据范围比较大,所以需要滚动数组,否则一个样例可能过不掉,同时需要记得取mod。

#include 
#include 
#include 
#include 
#include 

using namespace std;
const int N = 10000000 + 10, MOD = 1000000007;
typedef long long ll;

ll f[2][4];

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

H 扫雷

这道题终于会了,感觉是全场最难的一道题,因为需要手写哈希。原来的考点是图的遍历,怎么搞都可以,最难的在于哈希映射,一般采用开放寻址法和拉链法。如果使用字符串映射的话会TLE,那么这个时候只能选择整数映射,把坐标映射成 ( x ∗ 1 e 9 + 7 ) + y ) (x*1e9+7)+y) (x1e9+7)+y)这种形式

// shiran
#include 
using namespace std;

#define rep(i, a, n) for (int i = a; i < n; i++)
#define per(i, n, a) for (int i = n - 1; i >= a; i--)
#define sz(x) (int)size(x)
#define fi first
#define se second
#define all(x) x.begin(), x.end()
#define pb push_back
#define mk make_mair
typedef long long ll;
typedef pair<int, int> PII;
const int mod = 1e9+7;
const int N = 50010, M = 999997;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

int n, m;
struct Circle
{
    int x, y, r;
}p[N];
ll h[M], id[M];
bool st[M];

int sqr(int x)
{
    return x * x;
}

ll get_key(int x, int y)
{
    return (ll)x * INT_MAX + y;
}

int find(int x, int y)
{
    ll key = get_key(x, y);
    int t = (key % M + M) % M;

    while (h[t] != -1 && h[t] != key)
        if ( ++ t == M)
            t = 0;
    return t;
}

void dfs(int x, int y, int r)
{
    st[find(x, y)] = true;

    for (int i = x - r; i <= x + r; i ++ )
        for (int j = y - r; j <= y + r; j ++ )
        {
            if (sqr(i - x) + sqr(j - y) <= sqr(r))
            {
                int t = find(i, j);
                if (id[t] && !st[t])
                    dfs(i, j, p[id[t]].r);
            }
        }
}

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin >> n >> m;
    memset(h, -1, sizeof h);

    rep(i, 1, n + 1)
    {
        int x, y, r;
        cin >> x >> y >> r;
        p[i] = {x, y, r};

        int t = find(x, y);
        if (h[t] == -1) h[t] = get_key(x, y);

        if (!id[t] || p[id[t]].r < r)
            id[t] = i; // 只存最大的半径
    }

    while (m -- )
    {
        int x, y, r;
        cin >> x >> y >> r;

        for (int i = x - r; i <= x + r; i ++ )
            for (int j = y - r; j <= y + r; j ++ )
            {
                if (sqr(i - x) + sqr(j - y) <= sqr(r))
                {
                    int t = find(i, j);
                    if (id[t] && !st[t])
                        dfs(i, j, p[id[t]].r);
                }
            }
    }

    int res = 0;
    rep(i, 1, n + 1)
        if (st[find(p[i].x, p[i].y)])
            res ++ ;
    cout << res << endl;

    return 0;
}

G 李白打酒加强版

这道题也是一道DP,考试的时候太笨了,没有反过来去思考,赛后自己也直接写出来了,哎,还是自己太菜了。这道题状态很好想f[i][j][k]表示走过了i个店,遇见了j朵花,最后还有k壶酒的合法方案数,注意必须是合法的。
合法是什么意思呢?就是说现在的壶数量必须小于等于剩余花的数量。递推公式也比较简单,考虑最后一步不同的状态只有两种,就是最后碰到花还是店。如果说这次碰到了店,那么上一个店的时候只有这次酒壶的一半f[i][j][k]=f[i-1][j][k/2]。如果说这次遇到了花,那么上一次酒数量应该比这一次多一壶f[i][j][k]=f[i][j-1][k+1]。这道题边界也要考虑,就是说首先ij都需要合法,不能是负数,其次,得从0开始循环,也就是说,经过了0家店可以碰到花,同时遇花之前也可以去店里加酒,最后取模,还有就是最后的答案,不能花和店都用完了,因为最后一次必须是遇花,那么遇花为了合法所以必须还有酒,所以答案应该是f[n][m-1][1],OK,那么到这里这道题就已经很完美了,虽然代码很短,我刚考完就想到了呜呜呜。

#include 
#include 
#include 

using namespace std;
const int N = 110, MOD = 1000000007;

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

int main()
{
    cin >> n >> m;
    
    f[0][0][2] = 1;

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

砍竹子

其实这个题本质是一个最长公共下降子序列的问题,对于任意一个h[i],只要它高度降到了与前一个高度下降过程中的公共值,那么它就不需要花费代价继续下降,同时可以观察到高度下降的速度特别快(魔法就是厉害)。如果它降得的当前高度与前一个高度没有公共值,则需要多花费一个代价,来降低自己的高度。因此,可以用数组存储下降的花费和每次降低过程的值。时间复杂度为:O(n),因为每个数下降最多只会降低6次(我试过极限数据hhh)

#include 

using namespace std;
typedef long long LL;

const int N = 2e5 + 10;

LL a[N];
vector<LL> b[N];
int n;

LL solve(LL x) 
{
    return sqrt(x / 2 + 1);
}

int main() {
    scanf("%d", &n);

    for(int i = 1; i <= n; i ++ ) scanf("%lld", &a[i]);

    int res = 0;

    for(int i = 1; i <= n; i ++ ) {
        while(a[i] > 1) {
            int flag = 0;
            for(LL j : b[i - 1]) {
                if(a[i] == j) {
                    flag = 1;
                    break;
                }
            }
            if(!flag) res ++;
            b[i].push_back(a[i]);
            a[i] = solve(a[i]);
        }
    }
    
    printf("%d", res);

    return 0;
}

OK,非常感谢你能看到这里,最后我还是想再骂一下今年的线上比赛,真得是太蠢了,如果没拿省一我会再写一篇文章专门吐槽一下,请问大家,60分有机会省一嘛?哎——没机会去专心考研了,放弃算法竞赛了。

你可能感兴趣的:(蓝桥杯刷题,c++,算法,蓝桥杯,蓝桥杯省赛)