小记_CodeTON Round 8 D 题

文章描述了一种使用动态规划和优先队列优化的方法来解决涂色问题,给定成本矩阵,目标是找到所有可能涂色方式中代价前k大的组合。通过分析状态转移和优化合并操作,作者给出了一个时间复杂度为O(nklog(n))的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

nnn 个连续的方块,需要你去涂色,涂色的代价为:所有涂上颜色的连续的区间的代价之和。假设 000 表示未涂色, 111 表示有涂色。则 0 1 1 1 0 0 1 1 0 1 的涂色代价为 [1,4]+[7,8]+[10,10][1, 4] + [7, 8] + [10, 10][1,4]+[7,8]+[10,10]
所有的 [i,j][i, j][i,j] 由输入给出, 记为 ai,ja_{i, j}ai,j
求所有涂色方式中代价前 k 大的代价为多少。
数据范围是:
1≤n≤1031 \leq n \leq 10^31n103
−106≤ai,j≤106-10^6 \leq a_{i, j} \leq 10^6106ai,j106
1≤k≤min(2n,5∗103)1 \leq k \leq min(2^n, 5 * 10 ^3)1kmin(2n,5103)

解析:

1.首先思考,如果题目是求,所有涂色方式中代价最大为多少,怎么求?
对于这个问题,我们很容易想到动态规划进行转移。
dp[i]dp[i]dp[i] 表示当前已经考虑到第 1,2,......,i1, 2, ......, i1,2,......,i 块方块的涂色方式的最大代价。那就有第 iii 块方块涂不涂两种情况,我们可以分别从 dp[l]+a[l+2][i](l≤i−2)dp[l] + a[l+2][i] (l \leq i - 2)dp[l]+a[l+2][i]li2)dp[i−1]dp[i-1]dp[i1] 转移过来。因为如果要加上区间 al+2,ia_{l + 2, i}al+2,i 的代价,则第 l+1l + 1l+1 块方块不能涂色。
2.回归原题,前 k 大的代价怎么求?
可以设 dp[i]dp[i]dp[i] 为一个链表,存储已经考虑到第 1,2,......,i1, 2, ......, i1,2,......,i 块方块的涂色方式的所有代价。题目只需要求前 kkk 大的代价,则我们在上述状态转移的时候每个链表保存前 kkk 大的代价即可。然后就是用前 i−1i - 1i1个链表合并出第 iii 个链表的问题了。
朴素的做法,在前 i−1i - 1i1 个链表中,总共最多有 (i−1)∗k(i - 1) * k(i1)k 个代价,取最大的 kkk 个存入链表,如此时间复杂度为 O(n2klog(n∗k))O(n^2klog(n*k))O(n2klog(nk)),显然太慢。
如果链表中的元素有序,能优化吗?
3.接着思考,链表中的元素是有序的怎么做合并操作?
即有 nnn 个元素从大到小排序的链表,每个链表长度最大为 kkk, 将其合并成一个从大到小排序的链表。怎么做?
我们可以通过单调队列进行操作,先将每个链表的第一个元素放入单调队列中。弹出最大的元素,查看这个元素是属于第几个链表中的,将这个链表中的下一个元素放入单调队列中。时间复杂度 O(nklog(n))O(nklog(n))O(nklog(n))
同理
我们使链表单调不增的顺序排列,如此一层转移时间复杂度为 O(klog(n))O(klog(n))O(klog(n)),总共 nnn 层,则总时间复杂度为 O(nklog(n))O(nklog(n))O(nklog(n)) ,能通过此题。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int n, k;
int a[N][N];
int dp[N][5*N], cnt[N];
struct node {
    int val, idx, num;
    bool operator <(const node &a) const {
        return a.val > val;
    }
};
void solve() {
    cin >> n >> k;
    for (int i = 1; i <= n; i++)
        for (int j = i; j <= n; j++)
            cin >> a[i][j];
    for (int i = 1; i <= n; i++)
        cnt[i] = 0;
    cnt[0] = 1;
    for (int i = 1; i <= n; i++) {
        priority_queue<node> q;
        q.push({dp[i-1][1], i - 1, 1});
        for (int j = i - 2; j >= -1; j--)
            q.push({(j < 0 ? 0 : dp[j][1]) + a[j+2][i], j, 1});
        while (q.size() && cnt[i] < k) {
            node t = q.top(); 
            q.pop();
            dp[i][++cnt[i]] = t.val;
            if (t.idx < 0 || t.num >= cnt[t.idx]) continue;
            if (t.idx == i - 1)
                q.push({dp[t.idx][t.num+1], t.idx, t.num + 1});
            else q.push({dp[t.idx][t.num+1] + a[t.idx+2][i], t.idx, t.num + 1});
        }
    }
    for (int i = 1; i <= cnt[n]; i++)
        cout << dp[n][i] << ' ';
    cout << '\n';
}
int main() {
    int T;
    cin >> T;
    while (T--) 
        solve();
    return 0;
}

思考:

在这道题中的状态转移是否做到了不重不漏呢?即状态转移是否会有重复的某种涂色方式计入,或者缺少了某种涂色方式呢?

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值