正难则反,我们不顺应题目去想怎么才能搜出来子序列,而是反过来看数据库是不是把我们所有的可能都堵死了。
在这里使用双指针,一个指针指向数据库字符串,另一个指向当前的l和r,在这里我们从前往后遍历数据库字符串,如果出现了符合l和r中间要求的数字,那就记录下来并标记上这个数字,当当前的l和r的中间所有数字都出现过一次之后,就说明我们的所有可能性都被搜到了,那么我们就去搜下一个l和r。
如果到最后,数据库先搜完了或者说存在某一个l和r中间的所有可能性没有全部搜到,那就说明我们可以找到一个不是数据库的子序列的一个密码,否则如果l和r先被搜完了或者说所有可能性全部被搜到,那就无法设计出密码。
在一段数字中,如果有一个数字的某一个数位出现0,那么就会在与运算之后导致这个数位一直是0,那么如果对所有数字的数位进行前缀和计数处理,就可以在查找时判断出中间是否有某个数字的某个数位有0,如果有,那么结果上来看那个数位就要保持为0,否则就是1。
也就是说在l到r这一段中只有这一数位的1的个数是r - l + 1个,才能够保留下来为1。
这里有一个关键点,我们需要知道我们需要找到三个x值,那么分别让其对应到
x
1
≤
x
2
≤
x
3
x_1 \leq x_2 \leq x_3
x1≤x2≤x3
那么对于
x
1
+
x
2
2
\frac{x_1 + x_2}{2}
2x1+x2之前的数值我们就让第一个雕刻师雕刻,
x
1
+
x
2
2
\frac{x_1 + x_2}{2}
2x1+x2到
x
2
+
x
3
2
\frac{x_2+x_3}{2}
2x2+x3的数值就让第二位雕刻师雕刻,
x
2
+
x
3
2
\frac{x_2+x_3}{2}
2x2+x3之后的数值就让第三位雕刻师雕刻。
那么现在我们就实现了从之前的找三个值化为了找一个前缀一个后缀的问题。
这时候可以进行二分搜索答案,而让前后缀同时作为答案的两倍。
你可以将我们输入的序列想象成一个数轴,那么在划定两条线之后,左线一定不能大于右线,并且我们划定范围的最优解法是让三片被分割出来的区域长度尽可能的相等,因为我们最终的目的是最小化最远距离,那么当三片覆盖范围相同的时候一定是最优的。
此题难以理解,给出代码:
//二分部分
int l = 0,r = 1e9 + 10;
while(l < r){
int mid = l + r >> 1;
int i = 0;
while(i + 1 < a.size() && a[i] - a[0] <= 2*mid){
i++;
}
int j = n-1;
while(j - 1 >= 0 && a[n-1] - a[j] <= 2*mid){
j--;
}
if(i > j || a[j] - a[i] <= 2*mid){
r = mid;
}else {
l = mid + 1;
}
}
首先就应该主要到这个题出现的b是对角线对称的,所以如果我们确定出来每个数最左边出现的列数和最右边出现的列数就可以确定每个数的矩阵边长和,即2*(r-l)。
我们去正向遍历求l数组和反向遍历求r数组,并且维护一个最大值,如果出现了大于最大值的情况,那么就说明大于最大值到当前值这中间的所有数都是可以出现在这一列的,因为一个数只有遇上了大于自己的数才会被写入,那么如果当前我们有一个很大的数,就可以保证这一列都是可以把这个数当最较大值,就可以将小于其的所有颜色写入。
CODE:
void solve()
{
map<int,bool>vis;
cin >> n >> k;
for(int i = 1;i <= n;i++){
cin >> a[i];
vis[a[i]] = 1;
}
//most left
int now = 0;
for(int i = 1;i <= n;i++){
if(a[i] > now){
for(int j = now + 1;j <= a[i];j++)l[j] = i;
now = a[i];
}
}
//most right
now = 0;
for(int i = n;i >= 1;i--){
if(a[i] > now){
for(int j = now + 1;j <= a[i];j++)r[j] = i;
now = a[i];
}
}
//output
for(int i = 1;i <= k;i++){
if(vis[i])cout << 2*(r[i] - l[i] + 1) << " ";
else cout << 0 << " ";
}
cout << endl;
for(int i = 1;i <= k;i++)l[i] = r[i] = 0;
}