[Hdp] lc1787. 使所有区间的异或结果为零(线性dp+贪心+暴力到dp+难题+好题+思维)

13 篇文章 0 订阅
9 篇文章 0 订阅

1. 题目来源

链接:1787. 使所有区间的异或结果为零

2. 题目解析

依旧是自闭的一天…连续四天,四道 hard

本题按照 dp 分类的思想,在其中又贪心套 dp 可还行。

看一下是怎么从暴力 dp 优化到贪心+ dp 的,十分有价值


以下是学习笔记:

在这里插入图片描述
容易找到性质,得到周期性,得到要将每一列数变成一样的。

不考虑异或和为 0 的话就直接找每列的众数即可。

考虑异或和为 0 的话,就得考虑用 dp 了,f[i][j] 为前 i 列异或和为 j 的最小操作数。答案即为 f[k][0] 表示前 k 列异或和为 0 的最小操作数。

很明显其转移方程就是 f[i][j] = max(f[i][j], f[i-1][j ^ t]); 这个 t 就是这一列要改变到的哪个数,因为数据范围是 0~2^10,则 t 的范围就在 0~1023 中选即可:

则暴力 dp 即为:

// 其中 len[i-1] 表示第 i-1 列的数字个数
// son[i-1][j^t] 表示第 i-1 列,值为 j^t 的数字个数
// 两个相减就是第 i-1 列所有数变为 j^t 的操作数
for (int i = 1; i <= k; i ++ ) 
    for (int j = 0; j < 1024; j ++ ) 
        for (int t = 0; t < 1024; t ++ ) 
            f[i][j] = min(f[i][j], f[i-1][j ^ t] + (len[i-1] - son[i-1][j ^ t]));

这个方法足够暴力,时间复杂度到了 O ( 2 10 ∗ 2 10 ∗ k ) O(2^{10} * 2^{10} * k) O(210210k) 的复杂度,必然是超时了。

考虑怎么优化呢?得将一个循环优化掉啊。

我们是考虑了第 i 列的变为所有数的情况。这个情况其实可以再细分一下:

  • 某一列变为一个全新的数
  • 每一列变为原列中的一个数

如果是每一列都变为一个全新的数的话,肯定没有有某一列变为一个全新的数好。每一列全新,操作数必然是所有数,而某一列全新,是其他列的众数就不必操作!

故,这样就将暴力情况分解为两个情况,且优化掉了不可能的情况。

这样就不必枚举 t :0~1023 了。仅需要枚举本列中的数即可。

再和贪心解求个 min 即可。

这样问题就转化为上述的手写思考过程了!


时间复杂度 O ( n 2 ) O(n^2) O(n2) 三重循环,不过枚举列、再枚举行,其实就是枚举了整个二维空间,其实就是 O ( n 2 ) O(n^2) O(n2) 的复杂度。

空间复杂度 O ( n 2 ) O(n^2) O(n2)


代码:

class Solution {
public:
    int s[1024];    // 异或值最多是 1023,0~1023 共 1024 个数

    int minChanges(vector<int>& nums, int k) {
        // m 为分成长度为 k 的区间段个数,也是一列数的数量,注意上取整,最后一行可能不足 k 个数
        int n = nums.size(), m = (n + k - 1) / k;  
        vector<vector<int>> f(k + 1, vector<int>(1024, 1e8)); // f[i][j] 表示前 i 列异或和为 j 的最小操作数

        // 下标从 1 开始,初始化状态 0 列 长度为 0 异或值为 0 为合法方案,其余为非法方案
        f[0][0] = 0;
        int sum = 0, minv = 1e8;    // sum贪心解的操作数,minv 为所有列的最小众数
        for (int i = 1; i <= k; i ++ ) {    // 枚举每一列
            memset(s, 0, sizeof s);
            int len = m;                    // 求该列数的数量,可能不足 k 个数
            if (n % k && n % k < i) len -- ;    // 如果最后一列不足,则数量减一
            
            for (int j = 0; j < len; j ++ ) // 枚举行号,求第 i 列的数映射
                s[nums[j * k + i - 1]] ++ ; // i 从 1 开始

            // 求众数 maxv
            int maxv = 0;
            for (int j = 0; j < 1024; j ++ ) maxv = max(maxv, s[j]);

            // sum 求第 i 列的最小操作次数, minv 求最小众数
            sum += len - maxv, minv = min(minv, maxv);

            // dp, j u 的循环顺序改变不受影响
            for (int j = 0; j < 1024; j ++ )            // 枚举状态,前一列可能有 1024 种情况
                for (int u = 0; u < len; u ++ ) {       // 枚举选哪个数,从上往下选
                    int x = nums[u * k + i - 1], cost = len - s[x];     // 修改操作数是 len-s[x]
                    f[i][j] = min(f[i][j], f[i - 1][j ^ x] + cost);
                }
        }
        // 每列都是减去了众数的个数的,再补上最小众数的个数,它就是全新的一列
        return min(sum + minv, f[k][0]);
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 内容概要 《计算机试卷1》是一份综合性的计算机基础和应用测试卷,涵盖了计算机硬件、软件、操作系统、网络、多媒体技术等多个领域的知识点。试卷包括单选题和操作应用两大类,单选题部分测试学生对计算机基础知识的掌握,操作应用部分则评估学生对计算机应用软件的实际操作能力。 ### 适用人群 本试卷适用于: - 计算机专业或信息技术相关专业的学生,用于课程学习或考试复习。 - 准备计算机等级考试或职业资格认证的人士,作为实战演练材料。 - 对计算机操作有兴趣的自学者,用于提升个人计算机应用技能。 - 计算机基础教育工作者,作为教学资源或出题参考。 ### 使用场景及目标 1. **学习评估**:作为学校或教育机构对学生计算机基础知识和应用技能的评估工具。 2. **自学测试**:供个人自学者检验自己对计算机知识的掌握程度和操作熟练度。 3. **职业发展**:帮助职场人士通过实际操作练习,提升计算机应用能力,增强工作竞争力。 4. **教学资源**:教师可以用于课堂教学,作为教学内容的补充或学生的课后练习。 5. **竞赛准备**:适合准备计算机相关竞赛的学生,作为强化训练和技能检测的材料。 试卷的目标是通过系统性的题目设计,帮助学生全面复习和巩固计算机基础知识,同时通过实际操作题目,提高学生解决实际问题的能力。通过本试卷的学习与练习,学生将能够更加深入地理解计算机的工作原理,掌握常用软件的使用方法,为未来的学术或职业生涯打下坚实的基础。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值