题解 P4182 【[USACO18JAN]Lifeguards P】

· · 题解

P4182 题解

题意

提供 n 个区间,要求删去 k 个区间后剩下的区间覆盖长度最大。

(吐槽本题翻译,看英文才看懂的)

建模

我们先从一些特殊情况考虑。如果区间 A 完全覆盖区间 B ,那么删去这样的 B 区间完全没有用,因此我们先删除这种情况。

接下来,不难看出我们可以先对所有的区间进行一次排序,并且可以证明按照左端点排序或者右端点排序后,左端点和右端点都保持升序

证明如下:
如果存在左端点 l_i \leq l_{i+1}r_i \geq r_{i+1} ,那么区间 i 完全覆盖区间 i+1 ,这种情况在一开始的特殊情况考虑中就已经被删除。

然后我们就可以进行**区间** $dp$ 了。 根据题目,我们设 $f[i][j]$ 表示**前 $i$ 个区间,删除 $j$ 个的最大覆盖长度**。但是转移的时候,存在的问题是**并不确定覆盖的状态**,不方便转移。 具体化 $f[i][j][S]$ 表示**前 $i$ 个区间,删除 $j$ 个,状态为 $S$ 的最大覆盖长度**?爆空间。 因此我们最好把状态控制在 $2$ 维,且要具体化,因此我们可以这样定义: $f[i][j]$ 表示**前 $i$ 个区间,删除 $j$ 个的最大覆盖长度,**$\color{red}{\normalsize{\mathbf{并且限制选第}}}$ $\color{red}{i}$ $\color{red}{\normalsize{\mathbf{个}}}$。 转移方程: $$f[i][j]=\max\{f[k][j-\text{len}(k,i)],R[i]-\max(L[i],R[k])\}\ |\ k<i$$ 其中 $\text{len}(l,r)=r-l-1

复杂度 \Theta(n \times k^2) ,明显 \colorbox{#052242}{\text{\color{white}TLE}}

优化

我们继续把转移方程贴一遍:

f[i][j]=\max\{f[k][j-\text{len}(k,i)],R[i]-\max(L[i],R[k])\}\ |\ k<i

把它转化一下:

f[i][j]=\max\{f[k][k-\text{len}(k,i)],R[i]-L[i]\}\ |\ \text{\footnotesize{对于比较小的}}\ k

如果我们能够用预计算\Theta(k) 的转移变为 \Theta(1) 的,那么总复杂度即为 \Theta(nk) ,对于这种情况就 \colorbox{#52C41A}{\text{\color{white}{AC}}} 了。

这是对于较小的 k 的情况。

那么对于较大的 k 的情况?
这时的转移方程是:

f[i][j]=\max\{f[k][k-\text{len}(k,i)],R[i]-R[k]\}

que\_t 表示所有 f[k][k+t]-R[k] 的最大值,单调队列即可。

那如果 k 有限制,不能太小?还是单调队列

k 有限制,第二种情况在哪里求最大值?

并且,我们同时将 $f[i][j]$ 用 $que\_t[j-i+1]$ 来更新一下。 算完之后,更新单调队列,把 $f[i][j]-R[i]$ 插入到 $que\_t[j-i]$ 的单调队列里,即可。 那么我们理一下思路: 1. 计算 $f[i][j]$ 时,把 $que\_t[i-j-1]$ 中队首表示的区间不与 $i$ 相交的踢出去。 2. 当前队首是最大值。 3. 计算 $f[i][j]$ 并更新 $que\_t[i-j]$ 。 复杂度 $\Theta(nk)$ ,可以 $\colorbox{#52C41A}{\text{\color{white}{AC}}}$ 。 代码还是放一下吧,细节比较多,注意不要复制。 ```cpp //lifeguards P 防Copy版 for(ll i=1;i<=n*k;i++){ ll u=min(k+1,i); for(ll j=0;j<n;j++){ ll now_pos=i-j-1; while(!increasing_queue[now_pos].empty()&&sequence[increasing_queue[now_pos].front().node].r<sequence[i].l){ Node tt_next=increasing_queue[now_pos].front(); rr[now_pos]=max(rr[now_pos],tt_next.val+sequence[tt_next.node].r); increasing_queue[now_pos].pop_front(); } f[i][j]=max(f[i][j],rr[now_pos]+sequence[i].r-sequence[i].l); if(!increasing_queue[now_pos].empty())f[i][j]=max(f[i][j],increasing_queue[now_pos].front().val+sequence[i].r); ll now_val=f[i][j]-sequence[i].r; now_pos=i-j; while((!increasing_queue[now_pos].empty())&&(increasing_queue[now_pos].back().val<now_val))increasing_queue[now_pos].pop_back(); increasing_queue[now_pos].push_back((Node){i,now_val}); } }//在一定程度上参考了其他人的代码(我不会告诉你是因为我写的太丑了) for(int i=1;i<=n;i++)ans=max(ans,f[i][k]); ``` 完结撒花~ 看在我码了这么多字,给个赞吧![](https://cdn.jsdelivr.net/gh/xaoxuu/[email protected]/img/qq/%E5%8F%AF%E6%80%9C.gif)。