2024牛客寒假算法基础集训营4【难题详解】

文章讲述了如何使用动态规划方法解决一个涉及区间最大值的问题,包括状态转移方程的更新和空间优化策略,如使用一维数组、二维数组以及滚动数组来降低空间复杂度。最后提到与K.方块掉落问题的关联,可能是题目来源或者应用实例。

F. 来点每日一题

DP

由题容易想到的:

dp[i]表示 1-i 中求得的最大值, 所以最后ans为 dp[n]

状态转移与之前dp不一样的是,需要二维枚举,并且此题n的范围小,可以二维枚举:

dp[j]=max(dp[i-1]+cal(i,j),dp[j]);

cal(i,j)表示选 i~j 这个区间的最大值,如何计算?        还是DP

$\left(\left(b_1-b_2\right)\times b_3-b_4\right)\times b_5-b_6$

由于式子中有乘法,所以既要维护[i,j]区间的最大值,也要维护最小值

mx[i][j][1]=表示选了式子中的第一个数b1

状态转移:按选或不选第j个数划分

mx[i][j][1]=max(a[j],mx[i][j-1][1]);

mx[i][j][2]=max(mx[i][j-1][1]-a[j],mx[i][j-1][2]);

mx[i][j][3]=max(mx[i][j-1][2]*a[j],mx[i][j-1][3]);

mx[i][j][4]=max(mx[i][j-1][3]-a[j],mx[i][j-1][4]);

mx[i][j][5]=max(mx[i][j-1][4]*a[j],mx[i][j-1][5]);

mx[i][j][6]=max(mx[i][j-1][5]-a[j],mx[i][j-1][6]);

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define all(x) x.begin(),x.end()
#define no cout<<"No"<<endl
#define yes cout<<"Yes"<<endl
#define endl '\n'
// #define x first
// #define y second
typedef pair<int,int> PII;
const int N=200010;
const int mod=998244353;
const int INF=0x3f3f3f3f3f3f3f3f;
void solve(){
    int n;cin>>n;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    int mx[n+1][n+1][7]={};int mn[n+1][n+1][7]={};
    for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)for(int k=1;k<=6;k++)
        mx[i][j][k]=-INF,mn[i][j][k]=INF;

    for(int i=1;i<=n;i++){
        mx[i][i][1]=mn[i][i][1]=a[i];
        for(int j=i+1;j<=n;j++){
            for(int k=1;k<=6;++k)mx[i][j][k]=mx[i][j-1][k],mn[i][j][k]=mn[i][j-1][k];

            mx[i][j][1]=max(a[j],mx[i][j][1]);
            mn[i][j][1]=min(a[j],mn[i][j][1]);
            
            if(mx[i][j-1][1]!=-INF)mx[i][j][2]=max(mx[i][j-1][1]-a[j],mx[i][j][2]);
            if(mn[i][j-1][1]!=INF)mn[i][j][2]=min(mn[i][j-1][1]-a[j],mn[i][j][2]);

            if(mx[i][j-1][2]!=-INF)mx[i][j][3]=max(mx[i][j-1][2]*a[j],mx[i][j][3]);
            if(mn[i][j-1][2]!=INF)mx[i][j][3]=max(mn[i][j-1][2]*a[j],mx[i][j][3]);
            if(mx[i][j-1][2]!=-INF)mn[i][j][3]=min(mx[i][j-1][2]*a[j],mn[i][j][3]);
            if(mn[i][j-1][2]!=INF)mn[i][j][3]=min(mn[i][j-1][2]*a[j],mn[i][j][3]);
            
            if(mx[i][j-1][3]!=-INF)mx[i][j][4]=max(mx[i][j-1][3]-a[j],mx[i][j][4]);
            if(mn[i][j-1][3]!=INF)mn[i][j][4]=min(mn[i][j-1][3]-a[j],mn[i][j][4]);

            if(mx[i][j-1][4]!=-INF)mx[i][j][5]=max(mx[i][j-1][4]*a[j],mx[i][j][5]);
            if(mn[i][j-1][4]!=INF)mx[i][j][5]=max(mn[i][j-1][4]*a[j],mx[i][j][5]);
            if(mx[i][j-1][4]!=-INF)mn[i][j][5]=min(mx[i][j-1][4]*a[j],mn[i][j][5]);
            if(mn[i][j-1][4]!=INF)mn[i][j][5]=min(mn[i][j-1][4]*a[j],mn[i][j][5]);
            
            if(mx[i][j-1][5]!=-INF)mx[i][j][6]=max(mx[i][j-1][5]-a[j],mx[i][j][6]);
            if(mn[i][j-1][5]!=INF)mn[i][j][6]=min(mn[i][j-1][5]-a[j],mn[i][j][6]);
        
        }
    }

    vector<int>dp(n+1);
    for(int i=1;i<=n;i++){
        dp[i]=max(dp[i-1],dp[i]);
        for(int j=i+1;j<=n;j++){
            dp[j]=max(dp[i-1]+mx[i][j][6],dp[j]);
        }
    }
    cout<<dp[n]<<endl;
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int _=1;
    while(_--)solve();
    return 0;
}

注意:若不写for(int k=1;k<=6;++k)mx[i][j][k]=mx[i][j-1][k],mn[i][j][k]=mn[i][j-1][k];

只写if(mx[i][j-1][2]!=-INF)mx[i][j][3]=max(mx[i][j-1][2]*a[j],mx[i][j-1][3]);是不行的

因为if判断为false的话, mx[i][j-1][3] 这个状态就不会被考虑。

空间优化

mx[j][7]表示,在当前遍历的 i~j 中所满足的最大值

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define all(x) x.begin(),x.end()
#define no cout<<"No"<<endl
#define yes cout<<"Yes"<<endl
#define endl '\n'
// #define x first
// #define y second
typedef pair<int,int> PII;
const int N=200010;
const int mod=998244353;
const int INF=0x3f3f3f3f3f3f3f3f;
void solve(){
    int n;cin>>n;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    
    vector<int>dp(n+1);
    for(int i=1;i<=n;i++){

        int mx[n+1][7]={};int mn[n+1][7]={};
        for(int j=1;j<=n;j++)for(int k=1;k<=6;k++)
            mx[j][k]=-INF,mn[j][k]=INF;
        mx[i][1]=a[i],mn[i][1]=a[i];

        for(int j=i+1;j<=n;j++){
            for(int k=1;k<=6;++k)mx[j][k]=mx[j-1][k],mn[j][k]=mn[j-1][k];

            mx[j][1]=max(a[j],mx[j][1]);
            mn[j][1]=min(a[j],mn[j][1]);

            if(mx[j-1][1]!=-INF)mx[j][2]=max(mx[j-1][1]-a[j],mx[j][2]);
            if(mn[j-1][1]!=INF)mn[j][2]=min(mn[j-1][1]-a[j],mn[j][2]);

            if(mx[j-1][2]!=-INF)mx[j][3]=max(mx[j-1][2]*a[j],mx[j][3]);
            if(mn[j-1][2]!=INF)mx[j][3]=max(mn[j-1][2]*a[j],mx[j][3]);
            if(mx[j-1][2]!=-INF)mn[j][3]=min(mx[j-1][2]*a[j],mn[j][3]);
            if(mn[j-1][2]!=INF)mn[j][3]=min(mn[j-1][2]*a[j],mn[j][3]);
            
            if(mx[j-1][3]!=-INF)mx[j][4]=max(mx[j-1][3]-a[j],mx[j][4]);
            if(mn[j-1][3]!=INF)mn[j][4]=min(mn[j-1][3]-a[j],mn[j][4]);

            if(mx[j-1][4]!=-INF)mx[j][5]=max(mx[j-1][4]*a[j],mx[j][5]);
            if(mn[j-1][4]!=INF)mx[j][5]=max(mn[j-1][4]*a[j],mx[j][5]);
            if(mx[j-1][4]!=-INF)mn[j][5]=min(mx[j-1][4]*a[j],mn[j][5]);
            if(mn[j-1][4]!=INF)mn[j][5]=min(mn[j-1][4]*a[j],mn[j][5]);
            
            if(mx[j-1][5]!=-INF)mx[j][6]=max(mx[j-1][5]-a[j],mx[j][6]);
            if(mn[j-1][5]!=INF)mn[j][6]=min(mn[j-1][5]-a[j],mn[j][6]);
            
            dp[j]=max(dp[i-1]+mx[j][6],dp[j]);
        }
    }

    cout<<dp[n]<<endl;
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int _=1;
    while(_--)solve();
    return 0;
}

甚至还能用滚动数组继续优化

mx[7]表示,在当前遍历的 i~j 中所满足的最大值

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define all(x) x.begin(),x.end()
#define no cout<<"No"<<endl
#define yes cout<<"Yes"<<endl
#define endl '\n'
// #define x first
// #define y second
typedef pair<int,int> PII;
const int N=200010;
const int mod=998244353;
const int INF=0x3f3f3f3f3f3f3f3f;
void solve(){
    int n;cin>>n;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    
    vector<int>dp(n+1);
    for(int i=1;i<=n;i++){

        vector<int>mx1(7,-INF),mn1(7,INF);
        mx1[1]=mn1[1]=a[i];

        for(int j=i+1;j<=n;j++){
            vector<int>mx=mx1,mn=mn1;

            mx[1]=max(a[j],mx[1]);
            mn[1]=min(a[j],mn[1]);

            if(mx1[1]!=-INF)mx[2]=max(mx1[1]-a[j],mx[2]);
            if(mn1[1]!=INF)mn[2]=min(mn1[1]-a[j],mn[2]);

            if(mx1[2]!=-INF)mx[3]=max(mx1[2]*a[j],mx[3]);
            if(mn1[2]!=INF)mx[3]=max(mn1[2]*a[j],mx[3]);
            if(mx1[2]!=-INF)mn[3]=min(mx1[2]*a[j],mn[3]);
            if(mn1[2]!=INF)mn[3]=min(mn1[2]*a[j],mn[3]);
            
            if(mx1[3]!=-INF)mx[4]=max(mx1[3]-a[j],mx[4]);
            if(mn1[3]!=INF)mn[4]=min(mn1[3]-a[j],mn[4]);

            if(mx1[4]!=-INF)mx[5]=max(mx1[4]*a[j],mx[5]);
            if(mn1[4]!=INF)mx[5]=max(mn1[4]*a[j],mx[5]);
            if(mx1[4]!=-INF)mn[5]=min(mx1[4]*a[j],mn[5]);
            if(mn1[4]!=INF)mn[5]=min(mn1[4]*a[j],mn[5]);
            
            if(mx1[5]!=-INF)mx[6]=max(mx1[5]-a[j],mx[6]);
            if(mn1[5]!=INF)mn[6]=min(mn1[5]-a[j],mn[6]);
            
            dp[j]=max(dp[i-1]+mx[6],dp[j]);
            
            mx1=mx;
            mn1=mn;
        }

    }

    cout<<dp[n]<<endl;
}
signed main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int _=1;
    while(_--)solve();
    return 0;
}

K. 方块掉落

### 关于2020年寒假算法基础集训营中的欧几里得算法 在2020年的寒假算法基础集训营中,确实存在涉及欧几里得算法的相关题目。具体来说,在第四场竞赛的第一题即为“A. 欧几里得”,该题目的核心在于利用扩展欧几里得定理来解决问题[^5]。 #### 扩展欧几里得算法简介 扩展欧几里得算法主要用于求解形如 ax + by = gcd(a, b) 的线性不定方程的一组特解(x,y),其中gcd表示最大公约数。此方法不仅能够计算两个整数的最大公因数,还能找到满足上述条件的具体系数x和y。 对于给定的数据范围较小的情况可以直接通过递归来实现;而对于较大数据则需考虑效率优化问题。下面给出了一段基于C++语言编写的用于解决此类问题的模板代码: ```cpp #include<bits/stdc++.h> #define int long long using namespace std; // 定义全局变量存储结果 int x, y; void ex_gcd(int a, int b){ if(b == 0){ x = 1; y = 0; return ; } ex_gcd(b, a % b); int tmp = x; x = y; y = tmp - (a / b) * y; } ``` 这段程序实现了经典的扩展欧几里得算法逻辑,并且可以作为处理类似问题的基础工具函数调用。 #### 实际应用案例分析 回到原题本身,“A. 欧几里得”的解答思路就是先预处理斐波那契数列前若干项数值存入数组`a[]`内以便快速查询,之后针对每一次询问直接输出对应位置处两相邻元素之和即可得出最终答案。这实际上巧妙运用到了广为人知的裴蜀定理——任意一对互质正整数都可由它们自身的倍数组合而成,而这里正是借助了这一性质简化了解决方案的设计过程。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值