CodeTON Round 8 D 题

题意

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

解析:

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

思考:

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

  • 13
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值