题解十八:P16354 「Diligent-OI R3 B」天际线

· · 题解

【LGR-280-Div.2】洛谷 4 月月赛 III & Diligent-OI Round 3-P16354 「Diligent-OI R3 B」天际线。

卡了将近 1h,写篇题解纪念一下。

本题解为 O(nT) 做法。

篇幅略长。

思路

先考虑何时输出 -1

  1. 如果一栋楼,既要求“从北向南”被看到,又要求“两侧看时均不能被看见”,显然不合法;
  2. 同理,既要求“从南向北”被看到,又要求“两侧看时均不能被看见”,显然也不合法;
  3. 假如在最北端或最南端的楼,要求“两侧看时均不能被看见”,不合法;
  4. 找到要求“从北向南”被看到的楼房中最靠南的一个,找到要求“从南向北”被看到的楼房中最靠北的一个,若前者比后者更靠北,不合法。

:::info[为什么这么说?]{open} 记“要求‘从北向南’被看到的楼房中最靠南的一个”为 h_i,“要求‘从南向北’被看到的楼房中最靠北的一个”为 h_j

要求“从北向南”被看到,意味着在位置 i 的北面不能有比这栋楼高的楼,即 \forall k \in \{1, \dots, i-1\}, h_k < h_i

要求“从南向北”被看到,意味着在位置 j 的南面不能有比这栋楼高的楼,即 \forall k \in \{j+1, \dots, n\}, h_k < h_j

如果 j<i,那么按照定义,需要同时满足 h_i<h_jh_i>h_j,这显然不可能 :::

那么,对于有答案的问题,怎么去处理呢?

乍一看没有思路,我们不妨先尝试忽略所有的条件,直接构造。此时要达到字典序最小,直接升序排列即可。

然后加入 01a,显然此时原序列(即从 1n)仍是最优且合法的。因为串 a 本身就是要求其中为 1 的位置必须升序排列。

然后加入 01b,此时我们发现,之前的序列不对了。因为 b 串本身是要求其中为 1 的位置降序排列。

还记得刚刚我们找了一个“‘从南向北’被看到的楼房中最靠北的一个”吗?它就是 b 串中最靠前的 1 的位置。对于在它之前的位置,只有 a 要处理,因此仍按照之前的方法升序处理。

对于在它之后的位置,一种显然的思路是直接把剩下的数降序排列。但要注意:降序排列意味着字典序最差,对于在 b 串中为 1 的位置,没办法,只能降序;但对于为 0 的位置,完全可以升序处理!只需保证为 0 的高度都小于为 1 的高度即可。

所以此时的方法变成了:

找到 b 串中第一个 1 的位置记为 r,对于在 r 左侧的位置,令 h_i=i;右侧的位置,先将 b 中为 0 的位置仿照左侧,升序排列;再把剩下的位置从左到右、从 n 开始依次降低,即第一个为 1 的位置(r)为 n,下一个为 n-1,以此类推;

(当然,最后一部分也可以看做是从右到左,按刚刚最后一个数字 +1 开始依次升高,最后来到最大的位置,即 h_r=n。下面代码就是这种写法。)

代码大致如下:

ll num=1;
for(ll i=1;i<r;i++)//先处理左侧的,依次升高
    h[i]=num++;
for(ll i=r+1;i<=n;i++)//右侧为 0 的部分,依次升高
    if(b[i-1]=='0') h[i]=num++;
for(ll i=n;i>=r;i--)//剩下的,右向左升高(也可以左向右依次下降)
    if(b[i-1]=='1') h[i]=num++;

最后,考虑 c 串的情况。(由于合法判断的第三条,此处不考虑第一个位置与最后一个位置。)

由于 r 两侧处理不同,我们同样以 r 为分界。

对于 r 左侧的位置,首先它的右侧一定是看不见它的,因为它本身是升序排列的;假使它是升序部分最后一个,右边的 h_r 也是最大的 n

所以,左侧部分只需看是不是一个位置的左边有比它大的即可。

按照字典序最小的原则,肯定是这个大于它的位置越靠近它越好。假设左边紧邻的位置为 x,有连续 y 个位置不能被看见,那么 h_x 的最小值就应该是 x+y。而 剩下的 y 个位置则依次填入 xx+y-1

例如一个左半部分原本为:

1 2 3 4 5 6

现在要求第 345 个位置不能被看见。刚刚说过右边不考虑,因此让紧邻的左边的位置比它们大。这里有连续的 3 个位置不能被看见,左边紧邻的位置为 2,那么 h_2=3+2=5h_3=2h_4=3h_5=4。即:

1 5 2 3 4 6

## 代码 第一步,排除不合法的情况,同时找出 $r$; 第二步,处理 $r$ 左侧的位置; 第三步,处理 $r$ 右侧的位置。 ```cpp line-numbers #include<bits/stdc++.h> #define ll long long using namespace std; ll t,n,l,r,h[100010]; string a,b,c; void work(){ cin>>n>>a>>b>>c; l=0,r=n+1; if(c[0]=='1' or c[n-1]=='1'){//判断 3 cout<<-1<<'\n'; return; } for(ll i=1;i<=n;i++){ if(a[i-1]=='1') l=i; if(c[i-1]=='1' and (a[i-1]=='1' or b[i-1]=='1')){//判断 1,2 cout<<-1<<'\n'; return; } } for(ll i=n;i>0;i--) if(b[i-1]=='1') r=i; if(r<l){//判断 4 cout<<-1<<'\n'; return; } ll num=1; for(ll i=1;i<r;i++){//左侧部分 if(c[i]=='1'){//如果 i+1 位置的不能被看见 //注意 c 的下标小一个 for(ll j=i+1;j<r;j++){//向后把连续的部分处理了 if(c[j-1]=='0') break; h[j]=num++; }i=h[i]=num++;//把 h_i 更新 }else h[i]=num++;//否则,正常处理 } for(ll i=r+1;i<=n;i++) if(b[i-1]=='0') h[i]=num++; for(ll i=n;i>=r;i--) if(b[i-1]=='1') h[i]=num++; for(ll i=1;i<=n;i++){//输出部分 cout<<h[i]<<' '; h[i]=0;//多测要清空 }cout<<'\n'; return ; } int main(){ cin>>t; while(t--) work(); return 0; }//求赞 QAQ //293ms / 1.78MB / 824B C++98 O2 ``` ## 另外 欢迎接着看看月赛第一题的题解:《[题解十七:P16353 「Diligent-OI R3 A」说好不哭](https://www.luogu.com.cn/article/i8a9pe23)》。