《算法进阶指南》线性DP 饼干

2 题解

与排队打水类似,首先可以知道要让饼干数随着g[i]递减而递减。状态定义为前i个人共j个饼干的最小怒气值f[i][j]。
需要注意到当第i人饼干>1时,前i人共同减少一个饼干结果不变。当第i人饼干数=1时,遍历从第k个人开始饼干数都为1.
这样就能覆盖所有饼干分配方案。每一对[i][j]在这两种情况中曲最小值即可。
细节上,需要定义f[0][0]=0其他为INF,这是因为f[0][0]为n最小的合法情况(f[0][1],f[0][2]不合法)并且其他情况都可由f[0][0]扩展得出。
最后需要输出具体方案,在每个状态得出最小值时建立反边,最后从{n,m}反向回归,考虑不同状态转移时对答案的影响不同

struct Node
{
    int x;
    int y;
};
int n, m;
int f[31][5003];
int g[31], preg[31] = {0};
vector<Node> dag[31][5003];
int ans[31];
pair<int, int> id[31];
void solve()
{
    memset(f, 0x7f, sizeof(f));
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> g[i];
        id[i].first = g[i];
        id[i].second = i;
    }
    sort(id + 1, id + 1 + n, greater<pair<int, int>>());
    sort(g + 1, g + 1 + n, greater<int>());
    for (int i = 1; i <= n; i++)
    {
        preg[i] = preg[i - 1] + g[i];
    }

    f[0][0] = 0;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= m; j++)
        {
            if (j < i)
            {
                continue;
            }
            int x, y;
            for (int k = 1; k <= i; k++)
            {
                if (f[i][j] > f[k - 1][j - i + k - 1] + (preg[i] - preg[k - 1]) * (k - 1))
                {
                    f[i][j] = f[k - 1][j - i + k - 1] + (preg[i] - preg[k - 1]) * (k - 1);
                    x = k - 1, y = j - i + k - 1;
                }
            }
            int ok = 0;
            if (f[i][j] > f[i][j - i])
            {
                ok = 1;
                f[i][j] = min(f[i][j], f[i][j - i]);
                dag[i][j].push_back({i, j - i});
            }
            if (ok == 0)
            {
                dag[i][j].push_back({x, y});
            }
        }
    }
    int nowx = n, nowy = m, add = 0;
    while (dag[nowx][nowy].size())
    {
        for (int i = 0; i < dag[nowx][nowy].size(); i++)
        {
            int nextx = dag[nowx][nowy][i].x, nexty = dag[nowx][nowy][i].y;
            if (nextx == nowx)
            {
                add++;
            }
            else
            {
                for (int j = nowx; j > nextx; j--)
                {
                    ans[id[j].second] = add + 1;
                }
            }
            nowx = nextx, nowy = nexty;
        }
    }
    cout << f[n][m] << endl;
    for (int i = 1; i <= n; i++)
    {
        cout << ans[i] << ' ';
    }
}

signed main()
{

    ios_base::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    ios::sync_with_stdio(false), cin.tie(0);
    int T = 1;
    // cin >> T;

    for (int i = 1; i <= T; i++)
    {
        solve();
        // Init();
    }
    return 0;
}

3 收获

1.dp如何定义状态的方法,a.从题目中寻找能够减少状态总数的性质(这题为按g[i]递增,去除了非递增的饼干分配方案)(可能无法减少状态总数)b.设计的转移能够涵盖所有状态
2.需要求具体方案的dp,两种方法。方法a.在每个状态要转移时记录下唯一的转移路线(在能合法达到当前状态的所有状态取最值)最后从最终状态回推(状态本身不一定是答案,注意不同状态的转移对答案的影响)
方法b.从最终状态开始,遍历能合法抵达当前状态的所有状态(基本与正推相同),如果上一个状态经转移方程后等于当前状态,说明当前状态就是由这个状态转移得出,这样不断向前。
3.定义边界,保证不漏不多,不能让非法状态得到转移,并且从边界出发能涵盖所有需要的状态。

posted @ 2025-03-11 21:40  青一凡  阅读(11)  评论(0)    收藏  举报