第十三届蓝桥杯题解

1.x进制减法(贪心)


 

 思路:除了最高位和最底位为固定的,其余的要想满足其差最小,应该为max(2,max(a[i] + 1, b[i]+1)) 这样形成最小值

#include<iostream>
using namespace std;
typedef long long ll;
#define int long long
const int N = 1e5+3, p = 1000000007;
ll a[N], b[N], c[N];        // c[i] 为 第i个位的进制数
int n, m, q;

int main(){
    cin >> q >> n;
    for(int i=n; i>=1; i--) cin >> a[i];
    cin >> m;
    for(int i=m; i>=1; i--) cin >> b[i];

    for(int i=1; i<=n; i++){
        c[i] = max(2ll, max(a[i], b[i]) + 1);       // 初始化 c[],并保证c[i] >= 2
    }

    ll ans = 0;
    for(ll i = 1, j = 1; i <= n; i ++ ){            // i 表示位数,j 表示第i位的权值
        ans += j * (a[i] - b[i]) % p;
        ans = (ans % p + p) % p;                    // 因为有减法,所以需要放止下溢出
        j = 1ll * j * c[i] % p;                     // 更新权值
    }
    cout << ans << '\n';
    return 0;
}

2.统计子矩阵(双指针优化)

 

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1010;
ll g[N][N],g2[N][N];
ll n,m,k,ans = 0;

void add(int x,int y)
{
    g[x][y] += g[x][y-1];
    g[x][y] += g[x-1][y];
    g[x][y] -= g[x-1][y-1];
}

int main()
{
   cin>>n>>m>>k;
   for(int i = 1 ; i <= n ; i++)
   {
       for(int j = 1 ; j <= m ; j++)
       {
            int t;
            cin>>t;
            add(i,j);
            g[i][j] += t;
       }
   }
   for(int i = 1 ; i <= n ; i++)
   {
       for(int j = 1 ; j <= m ; j++)
       {
           for(int t = i ; t <= n ; t++)
           {
               for(int y = j ; y <= m ; y++)
               {
                   int q = g[t][y] - g[i-1][y] - g[t][j-1] + g[i-1][j-1];
                   if(q <= k){
                       ans++;
                   }   
               }
           }
       }
   }
   cout<<ans<<endl; 
}

优化思如:对每一行进行前缀和相加,那么我们会将一行变为一个维度,从而降低时间复杂度

#include <bits/stdc++.h>
using namespace std;
const int N = 510;
typedef long long LL;
int a[N][N];
int main()
{
    int n, m, k;
    scanf("%d%d%d", &n, &m, &k);
    
    for(int i = 1; i <= n; i ++ ) 
        for(int j = 1; j <= m; j ++ )
            scanf("%d", &a[i][j]);
            
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            a[i][j] += a[i][j - 1]; //每一行进行单独前缀和
   LL cnt = 0;//统计个数
    for(int i = 1; i <= m; i++){       //两列进行维度减少 n^4->n^3
        for(int j = i; j <= m; j++){
                for(int l = 1, r = 1 , sum = 0 ; r <= n ; r++){  //每行用两个指针进行维护
                    sum += (a[r][j] - a[r][i-1]); //对两列之间进行每一行相加 
                    while(sum > k && l <= r)   //由于从上到下每枚举一行那么该行可以和l到r进行任意组合,最多走2*n(第l到r,第l+1到r,……到r到r)符合一种单调性
                    {
                        sum -= (a[l][j] - a[l][i-1]);
                        l++;
                    }
                    cnt += (r - l + 1);    
            }
        }
    }
    printf("%lld", cnt);
    
    return 0;
        
}

3.积木画(类蒙的里安的梦想)

f(i,j) = 表示操作完前第i列的状态为j的所有方案的集合

 

 

#include<bits/stdc++.h>
using namespace std;
/*
f(i,j) = 表示操作完前第i列的状态为j的所有方案的集合
g(j,k) = 表示操作前i-1列的四种情况,可以使i列形成的情况数
i   i+1      f[i][j]对于每一列都会出现4种情况   
0    0       0   0   1   1     
0    0       0   1   0   1
*/
const int N = 1e7 + 10 , mod = 1e9 + 7;
int n;
int g[4][4] = {
    {1,1,1,1},
    {0,0,1,1},
    {0,1,0,1},
    {1,0,0,0},
};
/*
i-1  i  i+1             
 1   0   0  
 1   0   0
     1   0  g[0][0]   //当前一列是空的时候,可以这样摆,导致f[i+1][0] += f[i][0] * g[0][0]  
     1   0              第i+1列全空为第i列全空这样摆可以导致
     1   1  g[0][1]   
     1   0
     1   0  g[0][2]
     1   1
     1   1  g[0][3]
     1   1
     //其余情况
     1   0  g[1][2]   //当前一列是上面有一个的情况    
     0   0  g[1][3]
     
     1   0  g[3][0]   //当前一列一列都有的情况
     1   0  
     
     0   0  g[2][3]   //当前一列下面有一个的情况
     1   0  g[2][1]
*/

int f[N][4]; //每一列4种情况
int main()
{
    scanf("%d",&n);
    f[1][0] = 1;  //为什么这里f[1][0]是1,因为刚上来时第一列未放入,所以是1
    for(int i = 1 ; i <= n ; i++)
       for(int j = 0 ; j < 4 ; j++)
          for(int k = 0 ; k < 4 ; k++)
              f[i+1][k] = (f[i+1][k] + f[i][j] * g[j][k]) % mod;//上一个是空的,上一个是上面突出一个,下面突出一个,第二列全满
    //因为前一列影响下一列,所以我们只统计在前n列全满的情况下,n+1为空即可
    cout<<f[n+1][0]<<endl;
    return 0; 
}

 

 

 

 

//https://www.acwing.com/problem/content/293/
#include<iostream>
#include<cstring>

using namespace std;

//数据范围1~11
const int N = 12;
//每一列的每一个空格有两种选择,放和不放,所以是2^n
const int M = 1 << N;
//方案数比较大,所以要使用long long 类型
//f[i][j]表示 i-1列的方案数已经确定,从i-1列伸出,并且第i列的状态是j的所有方案数
long long f[N][M];
//第 i-2 列伸到 i-1 列的状态为 k , 是否能成功转移到 第 i-1 列伸到 i 列的状态为 j
//st[j|k]=true 表示能成功转移
bool st[M];
//n行m列
int n, m;

int main() {
//    预处理st数组
    while (cin >> n >> m, n || m) {
        for (int i = 0; i < 1 << n; i++) {
//            第 i-2 列伸到 i-1 列的状态为 k , 
//            能成功转移到 
//            第 i-1 列伸到 i 列的状态为 j
            st[i] = true;
//            记录一列中0的个数
            int cnt = 0;
            for (int j = 0; j < n; j++) {
//                通过位操作,i状态下j行是否放置方格,
//                0就是不放, 1就是放
                if (i >> j & 1) {
//                    如果放置小方块使得连续的空白格子数成为奇数,
//                    这样的状态就是不行的,
                    if (cnt & 1) {
                        st[i] = false;
                    }
                }else cnt++;
//                不放置小方格
            }

            if (cnt & 1) st[i] = false;
        }

//      初始化状态数组f
        memset(f, 0, sizeof f);

//      棋盘是从第0列开始,没有-1列,所以第0列第0行,不会有延伸出来的小方块
//      没有横着摆放的小方块,所有小方块都是竖着摆放的,这种状态记录为一种方案
        f[0][0] = 1; //f[0][0]不存在,所以f[0][0]为第一列前的一种情况,
        //f[m][0]因为第m列为最后一列,不能放,放即超界,所以f[m][0]这里用来统计所有情况
//        遍历每一列
        for (int i = 1; i <= m; i++) {
//            枚举i列每一种状态
            for (int j = 0; j < 1 << n; j++) {
//                枚举i-1列每一种状态
                for (int k = 0; k < 1 << n; k++) {
//                    f[i-1][k] 成功转到 f[i][j]
                    if ((j & k) == 0 && st[j | k]) { //j|k的意思为  i的j方案和i的k方案重合部分是否相符
                        f[i][j] += f[i - 1][k]; //那么这种状态下它的方案数等于之前每种k状态数目的和
                    }
                }
            }
        }
//       棋盘一共有0~m-1列
//       f[i][j]表示 前i-1列的方案数已经确定,从i-1列伸出,并且第i列的状态是j的所有方案数
//       f[m][0]表示 前m-1列的方案数已经确定,从m-1列伸出,并且第m列的状态是0的所有方案数
//       也就是m列不放小方格,前m-1列已经完全摆放好并且不伸出来的状态
        cout << f[m][0] << endl;
    }
    return 0;
}

 

4.扫雷(手写哈希)

 

 

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 50010, M = 999997;

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

LL get_key(int x, int y)
{
    return x * 1000000001ll + 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;
}

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

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, cir[id[t]].r);
            }
}

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

    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i ++ )
    {
        int x, y, r;
        scanf("%d%d%d", &x, &y, &r);
        cir[i] = {x, y, r};

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

        if (!id[t] || cir[id[t]].r < r)
            id[t] = i;
    }

    while (m -- )
    {
        int x, y, r;
        scanf("%d%d%d", &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, cir[id[t]].r);
                }
    }

    int res = 0;
    for (int i = 1; i <= n; i ++ )
        if (st[find(cir[i].x, cir[i].y)])
            res ++ ;

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

5.李白打酒(dp)

 

f(i,j,k) = i为店的个数,j为花的个数,k为酒的斗数 两种情况:走店,走酒 最后为花且酒为0, f(i,j-1,k+1) -> f(i,j,k) 最后一步  

#include<bits/stdc++.h>
using namespace std;
const int N = 110 , mod = 1e9 + 7;
int n,m;
int f[N][N][N];
int main()
{
    f[0][0][2] = 1;
    cin>>n>>m;
    for(int i = 0 ; i <= n ; i++)//i为店,j为花,k为酒
        for(int j = 0 ; j <= m ; j++)
            for(int k = 0 ; k <= m ; k++)//k最多有m斗,因为花不够,导致最后无法为0
            {
                //遇店
                if(i && k % 2 == 0) f[i][j][k] = (f[i][j][k] + f[i-1][j][k/2])%mod;
                //遇花
                if(j) f[i][j][k] = (f[i][j][k] + f[i][j-1][k+1])%mod; 
            }
    cout<<f[n][m-1][1]<<endl;
    return 0;
}

 

6.砍竹子

分析:一个数最多6次便可以运算到1,所以用f(i,j)储存第i个数的第j次形态 我们首先计算所有单个数据几次到1,然后对每一个数值进行看是可以归到一类 ,从而减少

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 200010 , M = 10;
ll n,m;
ll f[N][M];//计算该数每一层为几
ll stk[M],res = 0;
int main()
{
    scanf("%lld",&n);
    for(int i = 1 ; i <= n ; i++)
    {
        ll x;
        scanf("%lld",&x);
        ll top = 1;
        
        while(x > 1) stk[top++] = x , x = sqrt(x/2 + 1);
        
        m = max(m,top - 1);
        res += top - 1;
        
        for(int j = 1 , k = top - 1 ; k >= 1 ; k--,j++)
        {
             f[i][j] = stk[k];
        }
    }
     for(int i = 1 ; i <= n ; i++) 
         for(int j = 1 ; j <= m ; j++)
             if(f[i][j] && f[i][j] == f[i-1][j])
                    res--;
          
    printf("%lld",res);    
 }

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值