题目大意:
有n个木桶片环形排列高矮不一,有m次机会,每次选择一个长度为L的连续区间将高度变成无限长。
数据范围限制:
n<1000
L<20
m∗L<n
求计算操作之后如果希望最短的木板尽量高,能有多高?
做法分析:
考虑二分答案,对于一个确定的答案x,我们将木板中长度大于等于x的标记为0,小于x的标记为1。那么现在问题就转化成了:对于一个01环形序列,我能否用m个长度为L的区间将其中的1完全覆盖(区间可以重叠)。
下面考虑该转化后的子问题:设dp(i)表示前i个点中的1完全被覆盖最少需要的区间数,那么:
对于标记为1的位置:dp(i) = min{dp(pre1(i-L)) ,dp(i-L),dp(i-L+1),…dp(i-1)}+1
对于标记为0的位置:dp(i) = dp(pre1(i));
这里需要将序列循环右移L-1次(共L种不同01序列),每次分别进行一次dp,然后得到答案。
时间复杂度: O(log(2e9)∗L∗n∗L)
代码:
#include <iostream>
#include <vector>
#include <algorithm>
#include <stdlib.h>
using namespace std;
bool coverSeq01(const vector<int>& a, int m, int len) {
vector <int> dp;
/*printf("test:"); printf("%d %d\n ",m,len); for (int i = 0; i < a.size(); i++) { printf("%d", a[i]); } printf("\n");*/
int pre1 = -1;
for (int i = 0; i < a.size(); i++) {
if (a[i] == 0) {
if (pre1 == -1) {
dp.push_back(0);
}
else {
dp.push_back( dp[pre1] );
}
}
else {
int temp;
if (pre1 == -1) {
temp = 0;
}
else {
temp = dp[pre1];
}
for (int j = max(i - len, 0); j < i; j++) {
temp = min(temp, dp[j]);
}
dp.push_back(temp + 1);
pre1 = i;
}
}
/*printf(" "); for (int i = 0; i < a.size(); i++) { printf("%d", dp[i]); } printf("\n"); printf("ans:%d\n", dp[a.size() - 1] <= m);*/
return dp[a.size() - 1] <= m;
}
bool testMid(int limit, const vector<int>& a, int m, int len) {
for (int mov = 0; mov < len; mov++) {
vector<int>s;
for (int i = mov; i < a.size(); i++) {
if (a[i] < limit) {
s.push_back(1);
}
else {
s.push_back(0);
}
}
for (int i = 0; i < mov; i++) {
if (a[i] < limit) {
s.push_back(1);
}
else {
s.push_back(0);
}
}
if (coverSeq01(s, m, len) == true) {
return true;
}
}
return false;
}
int main()
{
int n, m, L;
while (cin >> n >> m >> L) {
vector <int> a;
int l = 2e9, r = 0;
for (int i = 0; i < n; i++) {
int temp = 0;
cin >> temp;
a.push_back(temp);
l = min(l, temp), r = max(r, temp);
}
while (l <= r) {
//cout << l << " " << r << endl;
int mid = (l + r) / 2;
if (testMid(mid, a, m, L) == true) {
l = mid + 1;
}
else {
r = mid - 1;
}
}
cout << l - 1 << endl;
}
}