請你將結果對1e9+7取模后再返回


來源:【C++ 取模mod易錯點】由於答案可能會很大,請你將結果對1e9+7取模后再返回_白馬金羈俠少年的博客-CSDN博客
在做算法題時我們經常會遇到這樣一句話:
由於答案可能會很大,請你將結果對10^9 + 7取模后再返回

附:為什么很多程序競賽題目都要求答案對 1e9+7 取模?

  1. 1000000007是一個質數
  2. int32位的最大值為2147483647,所以對於int32位來說1000000007足夠大
  3. int64位的最大值為2^63-1,對於1000000007來說它的平方不會在int64中溢出 所以在大數相乘的時候,因為(a∗b)%c=((a%c)∗(b%c))%c,所以相乘時兩邊都對1000000007取模,再保存在int64里面不會溢出

這句話看上去只要對變量取模就可以了,但實際上取模的時機有一定的講究,比如新手很容易犯一下兩個錯誤

錯誤1:用max比較很大數據時,先取模

取mod的時候,如果題目要求你算最大值,並且說由於答案可能很大,輸出結果請對1e9+7取,那你千萬不能在max函數更新最大值時就取模,這樣很可能會出錯

比如:題目過程中有四個數據

2e9+7,1e9+6,1e9+5,1e9+4

然后算法中你用max求最大值時,如果先模上1e9+7,那你會得到1e9,1e9+6,1e9+5,1e9+4,並且max函數算出的最大值是1e9+6,可是這四個數的最大值應該是2e9 + 7才對

正確做法:在求max的時候不要先取mod,而是都以long long型數據比大小,最后得到最大值是2e9 + 7,再對它取mod,得到結果是1e9 + 7

例題:1339. 分裂二叉樹的最大乘積

錯誤代碼示例

class Solution {
public:
    // 乘積 = 某個節點下所有子節點的和 *(整個樹的和 - 某個節點下所有子節點的和)
    typedef long long LL;
    LL rmax = 0;
    LL Total = 0;
    static const LL mod = 1e9 + 7;

    // 遍歷每個結點,更新最大值
    LL TreeSum(TreeNode* root) {
        if (!root) return 0;
        LL left = TreeSum(root->left);
        LL right = TreeSum(root->right);
        LL subsum = left + right + root->val;
		
		// 此處先取模,所以數據很大時會出錯
        rmax = max(rmax, (Total - subsum) * subsum % mod);
        return subsum;
    }

    int maxProduct(TreeNode* root) {
        if (!root) return 0;
        Total = TreeSum(root);
        rmax = 0;

        TreeSum(root);
        return rmax % mod;
    }
};

在這里插入圖片描述
類似題:5752. 子數組最小乘積的最大值

錯誤2:直到return時才取模

如果讓你算1+2+…+n的值(由於答案可能很大,輸出結果請對1e9+7取)
n的取值范圍是1 ~ 1 0 10000 10^{10000} 1010000

那顯然如果你在中間過程中不先取mod,必然會爆數據范圍,因為不管是int還是long long甚至是double(最大約 1 0 308 10^{308} 10308)都無法存下這個數據

...
long long res = 0;
for(int i = 1; i <= n ; i ++) {
    res = res + i; // n巨大,res無法存下這個數據
}

return res % (1e9 + 7);

正確做法:在算res的過程中取模

...
long long res = 0;
for(int i = 1; i <= n ; i ++) {
    res = (res + i) % (1e9 + 7); // n巨大,res無法存下這個數據
}
return res;

有的人會說這不是與錯誤1矛盾了嗎,其實完全不矛盾

  • 錯誤1是在比較多個很大的數據(不超過long long)
  • 錯誤2是在維護1個很大的數據,那你先取模和后取模,就沒有區別了,因為結果都是一樣的

另外有的時候我們經常喜歡用 += 之類的符號,但容易犯以下錯誤

long long res = 0;
for(int i = 1; i <= n ; i ++) {
    res += i % (1e9 + 7); // n巨大,res無法存下這個數據
}
return res;

上面的代碼實際上並沒有對res進行取模,而只是對 i i i進行取模,當res累加很多數后,是會超過long long,從而出現報錯的。所以你要么在下面加一行res %= (1e9 + 7),要么用res = (res + i) % (1e9 + 7);

現在看面試題:剪繩子II這個題,就知道為什么不能用動態規划了,因為取余之后max函數就不能用來比大小

數據較小時下面代碼是可以過的,數據大了就不行了,因為不能在max出取模

class Solution {
public:
    // f(n) = max(f(i) * f(n - i)) i:1,..,n-1
    int cutRope(int number) {
        // number = 1,2,3時剪一刀,值會變小,所以特殊考慮這幾種情況
        if(number < 2) return 0;
        if (number == 2) return 1;
        if (number == 3) return 2;
        
        int *f = new int[number + 1];
        f[1] = 1;
        f[2] = 2;
        f[3] = 3;
        
        // f(n) n大於3時,表示切若干刀后的最大值
        for (int i = 4; i <= number; i ++) {
            int rmax = number;
            for (int j = 1; j <= i / 2; j ++) {
                rmax = max(rmax, f[j] * f[i - j]);
            }
            f[i] = rmax;
        }
        
        int res = f[number];
        delete[] f;
        
        return res;
    }
};

錯誤3:是否真的取模了?

下面的式子取模過程有個坑點:

a[i][j]並沒有取模就直接與 pre[i][j]相乘了(*%是從左往右算的)

res = res % mod + pre[i][j] % mod * a[i][j] % mod;

正確的寫法:

res = res % mod + pre[i][j] % mod * (a[i][j] % mod);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM