优化dp&&贪心&&数论

这次三个题目都来自牛客周赛93,个人觉得出的很好,收获颇多。

1.简单贪心

题目意思:

任意选定两个数字,相加之和替代两个数字中的一个,另一个抹除。求操作之后最大字典序之和

思路:

最大字典序之和,给我们启发,第一个数字一定是最大的,所以再有限的k次操作中我们将除了数组中第一个数字之外,其他数字排序后的前k个数加入到第一个数字宜为最优方案。

特殊情况:

3 2 1 2 4 当k等于2的时候,该数组变成了下面这种

7 2 1 2,此时,后面最大的数字是2,那么这里就有两个方案(选择第一个,选择第二个)

第一个情况下:9 1 2

第二个情况下:9 2 1

不难看出当有重复数字出现的时候,我们选择最后一个数字宜为最优方案。(毕)

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define P pair<int,int>
const int op=2e5+5;
void solve() {
    int n, k;
    cin >> n >> k;
    vector<int> a(n + 1);
    priority_queue<P> answer;//优先队列
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
        if(i > 1) {
            answer.emplace(a[i], i);
            //加入除了第一个数的所有数字
        }
    }

    vector<int> vis(n + 1, 0);

    while(k--) {
        auto [val, id] = answer.top();
        answer.pop();
        //当val相等的时候,优先队列会将id比较大的那一个拿出来,也就是我们说的有多个数相等的时候取出最靠后的数字
        a[1] += val;
        vis[id] = 1;
    }

    for(int i = 1; i <= n; i++) {
        if(!vis[i]) {
            cout << a[i] << " ";
        }
    }
    cout << endl;
}

signed main() {
    int t = 1;
    cin>>t;

    while(t--) solve();
    return 0;
}

2.数论

题目意思:

给定一个数组求出非空子序列下,有多少个子序列满足Mex(S)>=Max(S)-Min(S)

思路:

第一考虑选择一个数的情况下(也就是说子序列里面只有1个可重复数字)

例如(1 1 1 )和(1)假设cnt[i]=k表示的是i数字有k个,那么方案数就是pow(2,k)-1(这里-1排除的是空的情况)

第二考虑多个数字情况下(0(若干个),1(若干个),2(若干个)....x(若干个))此时在这种情况Mex(S)>=Max(S)-Min(S)才成立。

假设中间缺了一个数字C,那么x(Max)-0(Min)一定是小于C的。

于是我们从0开始找到第一个没有出现的数字(假设是d)那么答案就是在(0(若干个),1(若干个),2(若干个)....d(若干个))中,此时的方案数和第一考虑的类似为:

pow(2,cnt\[0\])-1\]\*\[pow(2,cnt\[1\])-1\]...\*\[pow(d,cnt\[d\])-1

当然类似的,d-1也成立,于是:

pow(2,cnt\[0\])-1\]\*\[pow(2,cnt\[1\])-1\]...\*\[pow(d-1,cnt\[d-1\])-1

一直到0为止。

把第一考虑和第二考虑所有的方案数加在一起就是答案。(毕)

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
int nima(int a,int b){
    int sum=1;
    while(b){
        if(b&1){
            sum=(a*sum)%mod;
        }
        a=(a*a)%mod;
        b>>=1;
    }
    return sum;
    //打一个快速幂
}
signed main(){
    int n;cin>>n;
    vector<int>cnt(n+1);
    for(int i=1,x;i<=n;i++){
        cin>>x;
        cnt[x]++;
        //统计每一个数的个数
    }
    int ans=0;
    int s=1;
    for(int i=0;i<=n;i++){
        if(cnt[i]==0)break;
        //i这个数字不存在的时候就直接跳过,因为不连续了
        s*=nima(2ll,cnt[i])-1+mod;
        s%=mod;
        //s表示的是第i个子序列的方案数
        ans+=s;
        ans%=mod;
    }
    for(int i=1;i<=n;i++){
        //因为第0个已经在上面计算过了,后面我们直接从1开始计算
        ans+=(nima(2ll,cnt[i])-1+mod);
        ans%=mod;
    }
    cout<<ans<<endl;
}

3.优化dp问题

题目意思:

计算所有从第一行到第n行构成字符串中回文的数量。

思路:

暴力情况下我们进行思维dp[i1][j1][i2][j2],考虑是回文,我们从金字塔的中间开始往上往下进行同步跑,[i1][j1]表示的是向上的第i1行,第j1列,同理[i2][j2]表示的是向下的第i2列,第j2列。

但是由于n的范围比较大500,TLE,且MLE掉了。

继续优化,考虑到i1和i2到中间的距离一定是相等的,我们可以通过d来表示到中间的距离dp[d][j1][j2]。

该优化条件下TLE是不会了,但是如果常熟处理不好的情况下,还是会MLE的。

所以我们进行再次优化,考虑到赋值的情况下d+1的方案数只能是从d叠加过来的,故我们可以用&符号来进行滚动赋值。至此优化dp全部进行完毕。但是在滚动赋值的情况下,我们要清空上一个内容值。

细节:当n是偶数的情况下,我们对dp[0][j][j]进行初始化赋值,当n是奇数的情况下,我们只要对中间的那一行全部赋值1。

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int op=1e9+7;
signed main() {
    int n;
    cin >> n;
    int a[501][501];
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= i; j++) {
            cin >> a[i][j];
            int ans = 0;
            vector<vector<vector<int>>> dp(2, vector<vector<int>>(n + 3, vector<int>(n + 3)));
            //dp的初始化dp[2][501][501]
            if (n % 2 == 0) {//偶数的情况
                for (int j = 1; j <= n / 2; j++) {//对中间两行进行初始化
                    if (a[n / 2][j] == a[n / 2 + 1][j]) {
                        dp[0][j][j]++;
                    }
                    if (a[n / 2][j] == a[n / 2 + 1][j + 1]) {
                        dp[0][j][j + 1]++;
                    }
                }
                int m1 = n / 2, m2 = n / 2 + 1;
                //m1往上进行变量,m2往下进行遍历
                for (int d = 1; d <= n / 2 - 1; d++) {//
                    //这里就是滚动赋值的情况下,要将上一个状态清空
                    for (int j = 1; j <= n; j++) {
                        for (int k = 1; k <= n; k++) {
                            dp[d & 1][j][k] = 0;
                        }
                    }
                    for (int j = 1; j <= n; j++) {
                        for (int k = 1; k <= n; k++) {
                            if (a[m1 - d][j] == a[m2 + d][k]) {
                                //当上下两个数字都相同的时候,可以进行dp转移
                                //偶数的情况和期数的情况有点不同
                                dp[d & 1][j][k] += dp[(d - 1) & 1][j][k];
                                //根据题目所说一共有4种方案数的转移
                                dp[d & 1][j][k] += dp[(d - 1) & 1][j + 1][k];
                                dp[d & 1][j][k] += dp[(d - 1) & 1][j][k - 1];
                                dp[d & 1][j][k] += dp[(d - 1) & 1][j + 1][k - 1];
                                dp[d & 1][j][k] %= op;
                            }
                        }
                    }
                }
                for (int j = 1; j <= n; j++) {//
                    //最后求第1行n个数字的所有方案数即可
                    ans += dp[(n / 2 - 1) & 1][1][j];
                    ans %= op;
                }
                cout << ans << endl;
            } else {
                int m = n / 2 + 1;
                for (int j = 1; j <= m; j++) {
                    dp[0][j][j]++;
                }
                //奇数情况只要对中间的那一行进行赋值
                for (int d = 1; d <= n / 2; d++) {
                    for (int j = 1; j <= n; j++) {
                        for (int k = 1; k <= n; k++) {
                            dp[d & 1][j][k] = 0;
                        }
                    }
                    //这里和偶数的情况是一样的
                    for (int j = 1; j <= n; j++) {
                        for (int k = 1; k <= n; k++) {
                            if (a[m - d][j] == a[m + d][k]) {
                                dp[d & 1][j][k] += dp[(d - 1) & 1][j][k];
                                dp[d & 1][j][k] += dp[(d - 1) & 1][j + 1][k];
                                dp[d & 1][j][k] += dp[(d - 1) & 1][j][k - 1];
                                dp[d & 1][j][k] += dp[(d - 1) & 1][j + 1][k - 1];
                                dp[d & 1][j][k] %= op;
                            }
                        }
                    }
                }
                for (int j = 1; j <= n; j++) {
                    ans += dp[(n / 2) & 1][1][j];
                    ans %= op;
                }
                cout << ans << endl;
            }
        }
}
相关推荐
爱coding的橙子35 分钟前
每日算法刷题Day11 5.20:leetcode不定长滑动窗口求最长/最大6道题,结束不定长滑动窗口求最长/最大,用时1h20min
算法·leetcode·职场和发展
WenGyyyL37 分钟前
力扣热题——零数组变换 |
算法·leetcode·职场和发展·蓝桥杯
芯眼38 分钟前
AMD Vivado™ 设计套件生成加密比特流和加密密钥
算法·fpga开发·集成测试·软件工程
咪嗷喵挖藕哇39 分钟前
leetcode 合并区间 java
java·算法·leetcode
沐风ya40 分钟前
leetcode每日一题 -- 3355. 零数组变换 I
算法·leetcode
纪伊路上盛名在43 分钟前
leetcode字符串篇【公共前缀】:14-最长公共前缀
python·算法·leetcode
JK0x071 小时前
代码随想录算法训练营 Day52 图论Ⅲ 岛屿问题Ⅱ 面积 孤岛 水流 造岛
算法·深度优先·图论
Hygge-star1 小时前
【算法】定长滑动窗口5.20
java·数据结构·算法·学习方法·代码规范
好易学·数据结构1 小时前
可视化图解算法42:寻找峰值
算法
June`1 小时前
专题五:floodfill算法(图像渲染深度优先遍历解析与实现)
c++·算法·leetcode·深度优先·剪枝·floodfill