• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
nannandbk
博客园    首页    新随笔    联系   管理    订阅  订阅
2. 4 蓝桥练习5题

2. 4 蓝桥练习5题

今天写的几个题都挺有意思的,码量不大,主要是思维。藕还得多练QAQ

1.[P8669 蓝桥杯 2018 省 B] 乘积最大

题意:给定 \(N\) 个整数 \(A_1, A_2,\cdots, A_N\)。请你从中选出 \(K\) 个数,使其乘积最大。

请你求出最大的乘积,由于乘积可能超出整型范围,你只需输出乘积除以 \(1000000009\)(即 \(10^9+9\))的余数。

注意,如果 \(X<0\), 我们定义 \(X\) 除以 \(1000000009\) 的余数是 \(0-((0-x)\bmod 1000000009)\)。

思路:贪心+分类讨论

先排个序。如果\(K\)是偶数的话,我们考虑2个2个取。我们肯定优先两个正的或两个负的(保证答案是正数),实在没办法了才会考虑一正一负的情况(\(N=K的情况\))。我们排好序之后两头取,选大的。如果是偶数的话,把ans初始化为当前的最大值,注意如果当前最大值都是<0的(即全为负),那么最后介个也是负数,用\(f\)标记一下。然后k变成偶数,按照偶数是做法写。

// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 9;
const int N = 2e5 + 10;
ll a[N];
int main()
{
    ios::sync_with_stdio(false);   cin.tie(nullptr), cout.tie(nullptr);
    int n,k; cin>>n>>k;
    for(int i = 1;i <= n; i++)
        cin>>a[i];
    sort(a+1,a+1+n);
    ll ans = 1;
    int l = 1,r = n;
    int f = 1;
    if(k % 2){
        ans *= a[r];
        if(ans < 0)
            f = -1;
        r--;
        k--;
    }
    
    while(k)
    {
        ll pre = a[l]*a[l+1];
        ll suf = a[r]*a[r-1];
        if(f * pre > f * suf)
            ans *= pre%mod,ans %= mod,l += 2;
        else ans *= suf%mod,ans %= mod,r -= 2;
        k -= 2;
    }
    cout<<ans<<"\n";

    return 0;
}

2.[P8625 蓝桥杯 2015 省 B] 生命之树

题意:对于一棵树,找到其点权和最大的一个连通分量(注意可以为空),输出这个连通分量的点权和。

思路:树形\(dp\),考虑每个点为根\(u\)的情况下它的子树\(v\)最大点权和。

\(dp[u] += max(dp[v],0)\)

// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
int n,a[N];
vector<int>e[N];
ll dp[N];
void dfs(int u,int fa)
{
    //cout<<"u = "<<u<<"\n";
    dp[u] = a[u];
    for(auto v : e[u])
    {
        if(v == fa)continue;
        dfs(v,u);
        dp[u] += max(dp[v],0ll);
    }

}

int main()
{
    ios::sync_with_stdio(false);   cin.tie(nullptr), cout.tie(nullptr);
  
    cin>>n;
    for(int i = 1;i <= n; i++)
        cin>>a[i];
    for(int i = 1;i < n; i++)
    {
        int u,v; cin>>u>>v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs(1,0);
    ll ans = 0;
    for(int i = 1;i <= n; i++)
    {
        ans = max(ans,dp[i]);
    }
    cout<<ans<<"\n";
    return 0;
}

3.[P8782 蓝桥杯 2022 省 B] X 进制减法

题意:两个\(X\)进制数,求差最小值。最小为2进制。

思路:\(X\)进制是怎么求的呢?举个例子:最低数位为二进制,第二数位为十进制,第三数位为八进制,则\(X\)进制数\(321\)转化为十进制是什么呢?

\(a[i]\)数值:3 2 1

\(c[i]\)进制:8 10 2

\(d[i]\)权值:\(1\times2\times10\) \(1\times2\) 1

通式就是:\(d[i] = a[i]\prod_{k = 1}^{i-1}c[k](c[0] = 1)\)

那么对于\(X\)进制\(321\)转为\(10\)进制就是:\(20\times 3 + 2\times 2+1\times 1 = 65\)

好的了解了以上,我们来考虑本题。本题是要求差值最小。因为题目保证了\(A\ge B\),想要\(A-B\)的值尽量小,每一位的权值也要尽量小。因为是指数基本增长的,\(A\)变大会比\(B\)更快。如果想让每一位的权值尽量小,每一位的进制也得尽量小。那么我们考虑贪心,每一位的进制取\(max(a[i],b[i])+1\),如果\(<2\)的话置为\(2\)。记得开$long $ \(long\)。

// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
ll n,la,lb,a[N],b[N],c[N],d[N];
int main()
{
    ios::sync_with_stdio(false);   cin.tie(nullptr), cout.tie(nullptr);
    cin>>n;
    cin>>la;
    for(int i = la;i >= 1; i--)
        cin>>a[i];
    cin>>lb;
    for(int i = lb;i >= 1; i--)
        cin>>b[i];
    for(int i = 1;i <= max(la,lb); i++)
    {
        c[i] = max(a[i],b[i])+1;
        if(c[i]<2)c[i] = 2;
    }
    d[1] = 1;
    for(int i = 2;i <= max(la,lb);i++)
        d[i] = (d[i-1]*c[i-1])%mod;
    ll t1 = 0,t2 = 0;
    for(int i = 1;i <= la;i++)
        t1 += (a[i]*d[i])%mod,t1 %= mod;

    for(int i = 1;i <= lb;i++)
        t2 += (b[i]*d[i])%mod,t2 %= mod;
    cout<<(t1-t2+mod)%mod<<"\n";
    
    return 0;
}

4.[P8683 蓝桥杯 2019 省 B] 后缀表达式

题意:给定 \(N\) 个加号、 \(M\) 个减号以及 \(N+M+1\) 个整数 \(A_1,A_2,\cdots,A_{N+M+1}\),小明想知道在所有由这 \(N\) 个加号、 \(M\) 个减号以及 \(N+M+1\) 个整数凑出的合法的后缀表达式中,结果最大的是哪一个。

请你输出这个最大的结果。

例如使用 1 2 3 + -,则 2 3 + 1 - 这个后缀表达式结果是 \(4\),是最大的。

思路:这题很有意思,想了很久。后缀表达式是可以加括号滴。

比如

0 2
1 2 3

答案应该是4。因为3-(1-2) = 4。如果直接贪心的话求出来是3-2-1 = 0。

那么怎么办哩?

我们先考虑简单的,如果全是加号,那么答案一定是直接全部加起来就行了。

那么如果有减号存在呢?

一个负数通过加括号和减号来变成正数

对于负数\(a\)我们是不是可以\(-(a)\)变成正数。多个负数呢?比如\(a,b,c\)都是负数,那么\(-(a+b+c)\)就是正数了。

我们发现了什么?是不是只要有一个负号,我们就可以把若干个负数变成正数啦。我们考虑多加上大数,减去小数。我们考虑把max放首相,min放后面,构成以下表达式。

\(max-(min±①)±②\)

其他数:

  1. 正数 + :放在②
  2. 负数 - :放在②
  3. 正数 - : 放在①
  4. 负数 + :放在①

我们发现无论怎么放都是正数,完美解决。

// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;

ll a[N];
int main()
{
    ios::sync_with_stdio(false);   cin.tie(nullptr), cout.tie(nullptr);
    int n,m; cin>>n>>m;
    int k = n+m+1;
    for(int i = 1;i <= k; i++)
        cin>>a[i];
    sort(a+1,a+1+k);
    ll ans = 0;
    if(m==0)
    {
        for(int i = 1;i <= k; i++)
            ans += a[i];
    }
    else{
        ans = a[k]-a[1];
        for(int i = 2;i < k; i++)
            ans += abs(a[i]);
    }
    cout<<ans<<"\n";
    return 0;
}

5.[P8783 蓝桥杯 2022 省 B] 统计子矩阵

题意:给定一个 \(N \times M\) 的矩阵 \(A\),请你统计有多少个子矩阵 (最小 \(1 \times 1\), 最大 \(N \times M)\) 满足子矩阵中所有数的和不超过给定的整数 \(K\)。

思路:看见这个第一反应就是二维前缀和+枚举。枚举4维肯定是不行的,我就想枚举左上角和左下角的x再二分y。结果。。。还是TLE了hh。

// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
const int mod = 1e9 + 7;
const int N = 5e2 + 10;
ll n,m,k;
ll a[N][N],s[N][N];
bool judge(int x1,int y1,int x2,int y2)
{
    ll sum = s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1];
    return sum <= k;
}
signed main()
{
    ios::sync_with_stdio(false);   cin.tie(nullptr), cout.tie(nullptr);
    cin>>n>>m>>k;
    for(int i = 1;i <= n; i++)
        for(int j = 1;j <= m; j++)
            cin>>a[i][j];

    for(int i = 1;i <= n; i++)
        for(int j = 1;j <= m; j++)
            s[i][j] = s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];



    ll ans = 0;
    for(int i = 1;i <= n; i++)
    {
        for(int j = 1;j <= m; j++)
        {
            int last = m;
            for(int k = i;k <= n; k++)
            {
                int x1 = i,y1 = j;
                int x2 = k,y2;
                int l = j, r = last;
                while(l<=r)
                {
                    int mid = (l+r)>>1;
                    if(judge(x1,y1,x2,mid))l = mid+1;
                    else r = mid-1;
                }
                y2 = l-1;
                if(y2<y1)break;
                last = y2;
                ans += (y2-y1+1);
            }
        }
    }
    cout<<ans<<"\n";
    return 0;
}

以上代码只有80分。那么得把log优化掉怎么办哩。我们考虑双指针。

枚举\(x_1,x_2\),用双指针维护\(y_1和y_2\)

\(l\)表示\(y_1\),\(r\)表示\(y_2\)。我们不断右移\(r\),直到第一次不合法。那么当前\(r\)也不用再继续往后了,因为后面就更不行了,我们右移左指针\(l\),直到第一个合法位置,同时保证\(l\le r\)。然后更新答案,加上\(r-l+1\)。

// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
const int mod = 1e9 + 7;
const int N = 5e2 + 10;
ll n,m,k;
ll a[N][N],s[N][N];

signed main()
{
    ios::sync_with_stdio(false);   cin.tie(nullptr), cout.tie(nullptr);
    cin>>n>>m>>k;
    for(int i = 1;i <= n; i++)
        for(int j = 1;j <= m; j++)
            cin>>a[i][j];

    for(int i = 1;i <= n; i++)
        for(int j = 1;j <= m; j++)
            s[i][j] = s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];


    ll ans = 0;
    for(int i = 1;i <= n; i++)//x1
    {
        for(int j = i;j <= n; j++)//x2
        {
            for(int l = 1,r = 1;r <= m; r++)//y1,y2
            {
                while(l <= r &&s[j][r] - s[i - 1][r] - s[j][l - 1] + s[i - 1][l - 1] > k)l++;
                ans += r-l+1;
            }
        }
    }
    cout<<ans<<"\n";
    return 0;
}

posted on 2024-02-04 22:12  nannandbk  阅读(79)  评论(1)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3