题意
有
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
1≤n≤103
−
1
0
6
≤
a
i
,
j
≤
1
0
6
-10^6 \leq a_{i, j} \leq 10^6
−106≤ai,j≤106
1
≤
k
≤
m
i
n
(
2
n
,
5
∗
1
0
3
)
1 \leq k \leq min(2^n, 5 * 10 ^3)
1≤k≤min(2n,5∗103)
解析:
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](l≤i−2) 和
d
p
[
i
−
1
]
dp[i-1]
dp[i−1] 转移过来。因为如果要加上区间
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
i−1个链表合并出第
i
i
i 个链表的问题了。
朴素的做法,在前
i
−
1
i - 1
i−1 个链表中,总共最多有
(
i
−
1
)
∗
k
(i - 1) * k
(i−1)∗k 个代价,取最大的
k
k
k 个存入链表,如此时间复杂度为
O
(
n
2
k
l
o
g
(
n
∗
k
)
)
O(n^2klog(n*k))
O(n2klog(n∗k)),显然太慢。
如果链表中的元素有序,能优化吗?
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;
}
思考:
在这道题中的状态转移是否做到了不重不漏呢?即状态转移是否会有重复的某种涂色方式计入,或者缺少了某种涂色方式呢?