题解:P16348 「MierOI R1」Eternal (Hard ver.)

· · 题解

题意简述

给定若干个闭区间。 选出尽可能多的区间,要求:

任意两个相交的已选区间, 至少有一个端点相同。

解题思路

先单独记录退化区间 [i,i] 的数量 c_i。 其余区间都满足 l<r

f_i 为只使用端点不超过 i 的区间时, 最多可以选择多少个区间。

两个合法方案可以在坐标 i 处拼接。 左侧区间的右端点不超过 i, 右侧新增区间的左端点不小于 i。 若它们相交,交集只能是共同端点 i

下面只考虑右端点为 i 的新方案。

若没有选择任何右端点为 i 的非退化区间, 可以直接从 f_{i-1} 转移。

否则,设这些区间的最小左端点为 l。 于是,至少选择了一份最外层区间 [l,i]

考虑任何无法放入旧方案 f_l 的区间。 它的内部必须与 [l,i] 相交。 为了满足题意,它必须与 [l,i] 共用端点。

所以,这些新增区间只有两类:

左侧区间 [l,x] 与右侧区间 [y,i] 合法, 当且仅当 x\le y。 因此,存在一个分界坐标 k,满足:

x\le k\le y

先处理只选择一类区间的情况。

若新增区间都共用左端点 l, 可以把当前所有右端点不超过 i[l,x] 加入。 对应值为:

f_l+\#\{[l,x]\mid x\le i\}

扫描 i 时, 每遇到一份 [l,i],就把位置 l 的值加一。 代码用大根堆维护所有位置的当前值。 修改后压入新值,查询时丢弃过期记录。

若新增区间都共用右端点 i, 枚举其中最小的左端点 l。 排序后缀中的全部区间都可以选择,转移为:

f_l+\#\{[x,i]\mid x\ge l\}

最后处理左右两类区间同时存在的情况。 设 w(l,i) 是区间 [l,i] 的重数。

固定分界点 k, 最外层区间之外的可选数量为:

F(k)=\#\{[l,x]\mid x<i,x\le k\}+\#\{[y,i]\mid y>l,y\ge k\}

于是,对固定的 l,i,候选答案为:

f_l+w(l,i)+\max_{l<k\le i}F(k)

R_l 为左端点是 l 的右端点列表, 记 L_i 为右端点是 i 的左端点列表。 两个列表均保留重复元素并排序。

第二项只会在 $L_i$ 中的坐标减少。 若枚举 $R_l$, 相邻变化点之间第一项不变,第二项单调不增。 区间最大值一定在左端取得。 若枚举 $L_i$, 相邻变化点之间第二项不变,第一项单调不减。 区间最大值一定在右端取得。 所以,只需枚举 $R_l,L_i$ 中较短的列表。 另一项可以通过二分统计。 两侧边界再各检查一次即可。 处理完非退化区间后, 把全部 $[i,i]$ 加入答案。 它们与旧区间至多在端点 $i$ 相交, 所以一定可以全部选择。 下面估计枚举较短列表的总代价。 把每个不同的非退化区间看作一条边。 端点的度数按区间重数计算。 每条边的代价不超过两端度数的较小值。 把边朝度数较大的端点定向。 度数为 $d$ 的点至多连向 $d$ 个不同点。 它也至多连向 $2m/d$ 个度数不小于它的点。 因此,该点承担的代价至多为: $$ d\min\left(d,\frac{2m}{d}\right) $$ 分别考虑 $d\le\sqrt m$ 与 $d>\sqrt m$。 对全部端点求和可得 $O(m\sqrt m)$。 每次枚举还需进行二分。 所以,总时间复杂度为 $O(n\sqrt n\log n)$。 空间复杂度为 $O(n)$。 ## 参考代码 ```cpp #include <bits/stdc++.h> using namespace std; const int N=100005; vector<int> lft[N],rgt[N]; int cnt[N],f[N],g[N]; int calc(int k,int l,int r) { int x=upper_bound(rgt[l].begin(),rgt[l].end(),min(k,r-1))-rgt[l].begin(); int y=lft[r].end()-lower_bound(lft[r].begin(),lft[r].end(),k); return x+y; } void solve() { int n; cin>>n; int m=2*n; for(int i=1;i<=n;i++) { int l,r; cin>>l>>r; if(l==r)cnt[l]++; else { lft[r].push_back(l); rgt[l].push_back(r); } } for(int i=1;i<=m;i++) { sort(lft[i].begin(),lft[i].end()); sort(rgt[i].begin(),rgt[i].end()); } priority_queue<pair<int,int>> h; for(int i=1;i<=m;i++) { for(int l:lft[i]) { g[l]++; h.push({g[l],l}); } while(!h.empty()&&h.top().first!=g[h.top().second])h.pop(); int ans=f[i-1]; if(!h.empty())ans=max(ans,h.top().first); int s=lft[i].size(); for(int j=0;j<s;) { int l=lft[i][j]; int p=upper_bound(lft[i].begin()+j,lft[i].end(),l)-lft[i].begin(); ans=max(ans,f[l]+s-j); int val=0; if(rgt[l].size()<lft[i].size()) { val=calc(l+1,l,i); for(int k:rgt[l])if(l<k&&k<i)val=max(val,calc(k,l,i)); } else { val=calc(i,l,i); for(int k:lft[i])if(l<k&&k<=i)val=max(val,calc(k,l,i)); } ans=max(ans,f[l]+p-j+val); j=p; } f[i]=ans+cnt[i]; g[i]=f[i]; h.push({g[i],i}); } cout<<f[m]<<'\n'; for(int i=0;i<=m;i++) { lft[i].clear(); rgt[i].clear(); cnt[i]=f[i]=g[i]=0; } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin>>t; while(t--)solve(); return 0; } ```