算法可以发掘本质,如:
一,若干师傅和徒弟互有好感,有好感的师徒可以结对学习。师傅和徒弟都只能参加一个对子。如何让对子最多。
二,有无限多1X2和2X1的骨牌,某个棋盘若干格子坏了,如何在没有坏的格子放足够多骨牌。
三,某个单色图,1表示前前景,0表示后景色。每次操作可以将一个1,变成0。如何在最少得操作情况下,使得没有两个1相邻(四连通)。
四,若干路人,有些人是熟人,如何选出最多的人参加实验。为了避免熟人影响实验的效果,参加的人不能是熟人。
一二是二分图的最大匹配,三是二分图的最小点覆盖,四是二分图最大独立集。 而这三者是等效问题。
本文涉及知识点
位运算 反证法 试填法
LeetCode2897对数组执行操作使平方和最大
给你一个下标从 0 开始的整数数组 nums 和一个 正 整数 k 。
你可以对数组执行以下操作 任意次 :
选择两个互不相同的下标 i 和 j ,同时 将 nums[i] 更新为 (nums[i] AND nums[j]) 且将 nums[j] 更新为 (nums[i] OR nums[j]) ,OR 表示按位 或 运算,AND 表示按位 与 运算。
你需要从最终的数组里选择 k 个元素,并计算它们的 平方 之和。
请你返回你可以得到的 最大 平方和。
由于答案可能会很大,将答案对 109 + 7 取余 后返回。
示例 1:
输入:nums = [2,6,5,8], k = 2
输出:261
解释:我们可以对数组执行以下操作:
- 选择 i = 0 和 j = 3 ,同时将 nums[0] 变为 (2 AND 8) = 0 且 nums[3] 变为 (2 OR 8) = 10 ,结果数组为 nums = [0,6,5,10] 。
- 选择 i = 2 和 j = 3 ,同时将 nums[2] 变为 (5 AND 10) = 0 且 nums[3] 变为 (5 OR 10) = 15 ,结果数组为 nums = [0,6,0,15] 。
从最终数组里选择元素 15 和 6 ,平方和为 152 + 62 = 261 。
261 是可以得到的最大结果。
示例 2:
输入:nums = [4,5,4,7], k = 3
输出:90
解释:不需要执行任何操作。
选择元素 7 ,5 和 4 ,平方和为 72 + 52 + 42 = 90 。
90 是可以得到的最大结果。
提示:
1 <= k <= nums.length <= 105
1 <= nums[i] <= 109
位运算
性质一:假定某个最优解是res,res已经按升序排序。如果res.back()的某个二进制位为0,则整个res的此二进制位必定为0。反证法证明:
令 a < b,x = 1 << i ,a的第i个二进制位为1,b的第i个二进制位为0。
则两者之和为:y1= a2+b2
将a的对应1移到b后,两者之和为:
y2=(a-x)2+(b+x)2 = a2-2ax+x2+ b2+2bx+x2
y2-y1 = 2bx-2ax +2x2
2x2 > 0,且b >a ,则2bx-2ax> 0
故y2>y1。
同理删除res 最后的一个元素,余下元素也符合要求。
结论: res[i]的某个二进制位为0,则此二进制为res[j]也为0,j
原理
题中的操作将nums[j]的二进值1移到nums[i]。
操作方式:
将所有nums[j]的1全部移到nums[0] j > 0
将所有nums[j]的1全部移到nums[1] j > 1
⋮ \vdots⋮
思路
统计各二进制为1的个数。
从到大小构建res,如果cnt[j] > 0 则,第j位1,cnt[j]–;否则第j位为0。
初始化时间复杂度:O(nlog(max(n))
处理时间复杂度:O(klog(maxx(n)),处理时间可以优化到O(og(maxx(n)og(maxx(n)),因为除掉重复元素顶多log(max(n))个数,不同的数至少少一个二进制一。
代码
核心代码
template<int MOD = 1000000007> class C1097Int { public: C1097Int(long long llData = 0) :m_iData(llData% MOD) { } C1097Int operator+(const C1097Int& o)const { return C1097Int(((long long)m_iData + o.m_iData) % MOD); } C1097Int& operator+=(const C1097Int& o) { m_iData = ((long long)m_iData + o.m_iData) % MOD; return *this; } C1097Int& operator-=(const C1097Int& o) { m_iData = (m_iData + MOD - o.m_iData) % MOD; return *this; } C1097Int operator-(const C1097Int& o) { return C1097Int((m_iData + MOD - o.m_iData) % MOD); } C1097Int operator*(const C1097Int& o)const { return((long long)m_iData * o.m_iData) % MOD; } C1097Int& operator*=(const C1097Int& o) { m_iData = ((long long)m_iData * o.m_iData) % MOD; return *this; } bool operator==(const C1097Int& o)const { return m_iData == o.m_iData; } bool operator<(const C1097Int& o)const { return m_iData < o.m_iData; } C1097Int pow(long long n)const { C1097Int iRet = 1, iCur = *this; while (n) { if (n & 1) { iRet *= iCur; } iCur *= iCur; n >>= 1; } return iRet; } C1097Int PowNegative1()const { return pow(MOD - 2); } int ToInt()const { return m_iData; } private: int m_iData = 0;; }; class Solution { public: int maxSum(vector<int>& nums, int k) { const int iBitCnt = 31; int cnt[iBitCnt] = { 0 }; for (const auto& n : nums) { for (int j = 0; j < iBitCnt; j++) { if ((1 << j) & n) { cnt[j]++; } } } C1097Int<> biRet; while (k--) { int cur = 0; for (int j = 0; j < iBitCnt; j++) { if (cnt[j]) { cur |= (1 << j); cnt[j]--; } } biRet += (long long)cur * cur; } return biRet.ToInt(); } };
测试用例
template<class T> void Assert(const T& t1, const T& t2) { assert(t1 == t2); } template<class T> void Assert(const vector<T>& v1, const vector<T>& v2) { if (v1.size() != v2.size()) { assert(false); return; } for (int i = 0; i < v1.size(); i++) { Assert(v1[i], v2[i]); } } int main() { vector<int> nums; int k; { Solution sln; nums = { 2,6,5,8 }, k = 2; auto res = sln.maxSum(nums, k); Assert(261, res); } { Solution sln; nums = { 4,5,4,7 }, k = 3; auto res = sln.maxSum(nums, k); Assert(90, res); } }
我想对大家说的话 |
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。