洛谷題單指南-動態規劃2-P1874 快速求和

江城伍月發表於2024-04-25

原題連結:https://www.luogu.com.cn/problem/P1874

題意解讀:一個數字字串s,分解成幾個整數,和為n,計算最少加號個數,也就是計算最少分解的整數個數-1。

解題思路:此題雖然分類在動態規劃,但資料量不大,DFS更加直觀和易於理解,所以採用DFS暴搜+剪枝來解決。

搜尋思路是對數字字串依次列舉前1/2/3/4...個數字組成整數,直到整數超過目標和,然後遞迴處理字串剩下的部分,同時目標和減去前面的整數。

要想不超時,需要進行合理的剪枝:

剪枝1:當列舉到字串前面的整數是0,且不是最後一位時,沒必要繼續往下遞迴,這樣的拆分是多餘的,要去除前導0,繼續往後列舉

剪枝2:當列舉到字串前面的整數大於目標和,沒必要再列舉下去,直接退出迴圈

剪枝3:當列舉到字串前面的整數後,剩下部分的整數如果比剩下的目標和小,那麼繼續講剩下的字串拆分之後的和會越來越小,沒必要再列舉下去,退出迴圈

100分程式碼:

#include <bits/stdc++.h>
using namespace std;

string s;
int n;
int ans = INT_MAX;

//從字串s的start開始,挑選出若干整數之和等於sum,cnt記錄有多少個整數
void dfs(int start, int sum, int cnt)
{
    if(start == s.size() && sum == 0)
    {
        ans = min(ans, cnt - 1); //加號數量是整數數量-1
        //cout << cnt - 1 << endl;
        return;
    }
    int num = 0;
    for(int i = start; i < s.size(); i++)
    {
        num = 10 * num + s[i] - '0'; //列舉前0~i數字組成的整數
        if(num == 0 && i < s.size() - 1) continue; //剪枝1:整數的前導0沒必要單獨抽取出來,最後的0需要單獨抽取出來
        if(num > sum) break; //剪枝2:如果整數超過sum,結束,因為再往後num更大
        if( s.size() - i <= 15)
        {
            long long remain = 0;
            for(int j = i; j < s.size(); j++)
            {
                remain = 10 * remain + s[j] - '0';
            }
            if(remain < sum - num) break; //剪枝3:如果字串剩下部分的整數比sum-num還小,後續的劃分只會越來越小,可以結束
        }
        dfs(i + 1, sum - num, cnt + 1); //前0~i個數字劃為一個整數,繼續處理後面的數字,'+'加1
    }
}

int main()
{
    cin >> s >> n;
    dfs(0, n, 0);
    if(ans == INT_MAX) cout << -1;
    else cout << ans;
    return 0;
}

相關文章