题解 P1528 【切蛋糕】

· · 题解

此题算法Tag:二分查找、深度优先搜索、贪心、剪枝

分析

搜索

优化

说明

代码

#include <iostream>
#include <algorithm>

#define max(a,b) (a>b?a:b)
#define MIN_NEED mouth[1]

using namespace std;

int n,m;
int cake[55],mouth[1050];

int prefixSum[1050];                // 排序后嘴大小的前缀和
int maxCake,allCake;                // 最大蛋糕和所有蛋糕总和
int totalCake,needCake;             // 搜索:当前全部可用蛋糕和还需要的蛋糕
int wasteCake;                      // 优化:浪费的蛋糕渣

bool sub_DFS(int toTest, int origin)            // origin:遍历蛋糕数组的起点
{
    /* 结束递归 */
    if(toTest<1)
        return true;                            // 已经完成1~toTest的人的喂食,测试通过
    if(totalCake-wasteCake<needCake)
        return false;                           // 总蛋糕小于总需求,必然失败,停止搜索

    /* 搜索 */
    bool flag = false;
    for(int i=origin;i<=n;++i)                  // 遍历蛋糕,尝试将蛋糕喂给第toTest号人
    {
        if(cake[i]>=mouth[toTest])              
        {
            needCake-=mouth[toTest];            // 喂食:消耗蛋糕,满足需求                 
            totalCake-=mouth[toTest];
            cake[i]-=mouth[toTest];

            bool wasted = false;                // 回溯:是否使用了蛋糕渣优化
            if(cake[i]<MIN_NEED)                // 优化:蛋糕渣不能满足最低需求,不可用
            {
                wasteCake+=cake[i];             // 此蛋糕渣将被浪费,设置优化启动的标志
                wasted = true;
            }
            if(mouth[toTest]==mouth[toTest-1])  // 下一个测试对象的嘴和当前的嘴一样大
            { 
                if(sub_DFS(toTest-1,i))         // 优化:从当前的位置继续遍历蛋糕列表
                flag = true;                    // 优化:找到解决方案,就不再继续搜索
            }
            else if(sub_DFS(toTest-1,1))        // 无法优化,直接递归。
                flag = true;                    // 优化:同上分支

            /* 回溯 */
            if(wasted)                          // 若做了优化,则撤回         
                wasteCake-=cake[i];                
            cake[i]+=mouth[toTest];             // 撤回全部变化
            totalCake+=mouth[toTest];
            needCake+=mouth[toTest];

            if(flag) return true;
        }
    }

    /* 结束递归 */
    return false;                               // 无法找到合适的蛋糕,测试失败
}

inline bool DFS(int toTest)                     // DFS:检查答案toTest是否可行
{
    /* 准备变量 */
    totalCake = allCake;                          
    needCake = prefixSum[toTest];
    wasteCake = 0;

    /* 启动递归 */
    return sub_DFS(toTest,1);
}

int main()
{
    /* 优化读写 */
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    /* 初始化 */
    cake[0] = mouth[0] = 0;
    prefixSum[0] = 0;
    maxCake = allCake = 0;

    /* 读入 */
    cin>>n;
    for(int i=1;i<=n;++i)
    {
        cin>>cake[i];
        maxCake=max(maxCake,cake[i]);           // 找到最大蛋糕并计算总值
        allCake+=cake[i];
    }
    cin>>m;
    for(int i=1;i<=m;++i)
        cin>>mouth[i];

    /* 预处理 */
    sort(mouth+1,mouth+1+m);                    // 贪心:先满足小嘴,进行升序排序
    for(int i=1;i<=m;++i)
        prefixSum[i]=prefixSum[i-1]+mouth[i];   // 计算前缀和    

    /* 二分查找 */
    int l=1,r=m;
    while(mouth[r]>maxCake)--r;                 // 优化:缩小二分查找范围
    int mid;
    while(l<=r)
    {
        mid = ((l+r)>>1);
        if(DFS(mid))l=mid+1;                 
        else r=mid-1;                       
    }
    cout<<r;                               

    return 0;
}

注释写的还算详细吧……就算文字说明的很屑应该也能看懂代码吧==

2019-11-09-5:55-A.M> 狗命要紧,洗洗睡罢()

此题还可以使用神秘的随机化算法