leetcode 3266 K次乘运算后的最终数组II 题解

题目大意

原题面
给你一个数组 nums,然后进行 k 轮游戏,每轮游戏都会选择数组当中最小的元素然后乘上一个数 multiplier(题目给出),问你 k 轮游戏结束之后,这个数组长什么样子,所有的元素要对 1e9 + 7 取模。

1 ≤ n u m s . l e n g t h ≤ 1 0 4 1 ≤ n u m s [ i ] ≤ 1 0 9 1 ≤ k ≤ 1 0 9 1 ≤ m u l t i p l i e r ≤ 1 0 6 1 \le nums.length \le 10^4\\ 1 \le nums[i] \le 10^9\\ 1 \le k \le 10^9\\ 1 \le multiplier \le 10^6 1nums.length1041nums[i]1091k1091multiplier106

题解

我一看数据范围,第一反应是用矩阵快速幂,但是显然不现实,没思路怎么办?那么就不得不提到我个人最擅长的算法,打表+大眼球观察法。

我们就用题目给出的样例1进行举例。

nums = [2,1,3,5,6], k = 5, multiplier = 2

数组的变化如下:
打表
我们发现在元素的选择上,貌似当数组达成了某些条件之后,元素的选择就会陷入循环。

当 k 非常大的时候,很显然整个数组都会变得 “差不多一样大”,因为每次对最小的元素进行一个乘法操作,当 k 非常大的时候,数组中所有的元素都会变成 multiplier 的某次方然后乘上一个很小的数,这个数就是题目中给出的数组中的元素(相对于乘上的数很小)。当我们继续选择一个元素去进行乘法的时候,这个元素就会变得非常的大,比其他所有元素都要大,只有所有的元素都完成了一次乘法操作之后,这个元素才有可能被再次做乘法。

那么这里我们就把 “某些条件” 找出来了,就是当数组中所有的元素全都满足做完乘法以后是当前数组中最大的元素,重复的选择就开始了。

这个时候我们把这个数组进行排序,此时的顺序,就是重复进行乘法操作的顺序。

例如上面这张图上的样例,当数组变成了 [8, 8, 6, 5, 6] 的时候,其中所有的元素都满足乘以 2 之后是数组的最大值,后面就开始进行了重复的乘法操作。然后就开始开始按照 1, 2, 4, 3, 5 的顺序进行选择。

代码

const int MOD = 1e9 + 7;

struct num {
    int pos, w;
    bool operator <(const num& tmp) const
    {
        if (w == tmp.w)
            return pos < tmp.pos;
        return w < tmp.w;
    }
};

struct Compare {
    bool operator()(const num& a, const num& b) const
    {
        if (a.w == b.w)
            return a.pos > b.pos;
        return a.w > b.w;
    }
};

int solve(int x, long long multiplier ,int p) // 用来计算 x 进行 p 次乘法之后的结果
{
    long long ans = x;
    while (p)
    {
        if (p & 1)
            ans *= multiplier, ans %= MOD;
        multiplier *= multiplier, p >>= 1, multiplier %= MOD;
    }
    return ans;
}

class Solution {
public:
    vector<int> getFinalState(vector<int>& nums, int k, int multiplier)
    {
        const int n = nums.size();
        if (multiplier == 1)
            return nums;
        vector<int> p(n + 5, 0);
        int cnt = 0, maxdata = 0;
        for (int i = 0; i < n; i++)
            maxdata = max(maxdata, nums[i]);
        vector<int> a = nums;
        int K = k;
        for (int i = 0; i < n; i++) // 先把数组变成所有元素乘二都会变成最大值的状态
        {
            while ((long long)a[i] * multiplier <= maxdata && K >= -1)
            {
                a[i] *= multiplier;
                K--;
                p[i]++;
            }
        }
        if (K < 0) // 还没变成想要的状态就结束了,这个时候 k 一定很小,直接用优先队列模拟
        {
            priority_queue<num, vector<num>, Compare> q;
            for (int i = 0; i < n; i++)
            {
                q.push(num{i, nums[i]});
            }
            for (int i = 1; i <= k; i++)
            {
                num tmp = q.top();
                q.pop();
                nums[tmp.pos] *= multiplier;
                q.push(num{tmp.pos, nums[tmp.pos]});
            }
        }
        else // 不然就要开始进行重复的乘法操作
        {
            k = K;
            for (int i = 0; i < n; i++)
                p[i] += k / n; // 每个元素平均分配
            vector<num> q;
            for (int i = 0; i < n; i++) // 排序,此时的 a[] 满足我们想要的条件,后面的选择按照此时的大小顺序进行选择
                q.push_back(num {i, a[i]});
            sort(q.begin(), q.end());
            for (int i = 0; i < k % n; i++)
                p[q[i].pos]++;
            for (int i = 0; i < n; i++) // 求结果
                nums[i] = solve(nums[i], multiplier, p[i]);
        }
        return nums;
    }
};





我个人的数学水平不太好,希望这种说法能让大家理解!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值