【题解】CF1348F Phoenix and Memory

· · 题解

题意简述

问是否存在唯一的长度为 n 的排列 p,满足 a_i \le p_i \le b_i \; (1 \le i \le n)

数据保证至少存在一个符合题意的排列。

如果唯一,输出这个排列;否则输出任意两种不同的满足题意的排列。

数据范围1 \le n \le 2 \times 10^5

分析 + 题解

首先考虑如何求出一个可行的排列。

事实上,这可以转化为一个简单的贪心问题——

具体实现可以**把线段按左端点排序**,然后从小到大枚举 $i$,对于每个 $i$,先将左端点为 $i$ 的线段丢进堆(优先队列)里,然后从中**取出右端点最小的线段**,与 $i$ 号点匹配即可。 由于我们在讨论 $i$ 号点之前,所有包含 $i$ 号点且未匹配的线段均在堆中,因此我们贪心选取其中右端点最小的策略是正确的。 (右端点越靠右,这条线段就有越多的选择的机会) 那么如何求出另外一个满足题意的排列呢?(或者发现无解) ------------ 引理:**若存在另外一个满足题意的排列,我们一定可以通过交换已知排列中的其中两个数来转化成它。** 证明: 在上述条件下,必然存在一个长度 $\ge 2$ 的循环。(这是修改排列以保证仍然满足题意的充要条件)接下来让我们证明必然存在一个长度 $=2$ 的循环。 假设不存在长度 $=2$ 的循环,则必然存在一个**最短的**长度 $\ge 3$ 的循环。 设 $y$ 为这个循环中最左边的一个点,$x$ 是 $y$ 的前驱,$z$ 是 $y$ 的后继(即 $y$ 可以改为匹配 $x$ 当前对应的线段,$z$ 可以改为匹配 $y$ 当前对应的线段),如图所示: ![](https://cdn.luogu.com.cn/upload/image_hosting/3teeb84e.png) 请注意 $y$ 当前对应线段的右端点一定在 $x$ 左侧,否则 $x$ 和 $y$ 就会形成一个长度为 $2$ 的循环。 而我们可以发现,此时移除 $y$ 没有任何影响,因为 $z$ 可以改为匹配 $x$ 当前对应的线段。于是我们可以获得一个更小的环,这与假设矛盾,故引理成立。 ------------ 于是我们只需要求出需要交换的 $p_i$ 和 $p_j$,以满足 $a_i \le p_j \le b_i$,且 $a_j \le p_i \le b_j$。 不妨设 $p_i < p_j$,则条件转化为 $a_j \le p_i < p_j \le b_i$。枚举 $i$,发现只需查询是否存在 $j$ 满足 $a_j \le p_i$($p_i$ 为定值)且 $p_i < p_j \le b_i$($p_i,b_i$ 为定值)。 我们设 $id$ 为 $p$ 的逆映射,即 $id_{p_i}=i$,将 $i$ 的枚举顺序改为 $id_1$,$id_2$,$\cdots$,$id_n$,每次先把 $a_j=p_i$ 的 $j$ 对应的 $p_j$ 扔进 set,然后查询 set 中是否存在值 $\in (p_i,b_i]$,二分查找即可。 ## 代码 查询方法见注释。 ``` cpp #include<bits/stdc++.h> using namespace std; const int max_n=2e5+5; struct node { int a,b,id; }c[max_n]; vector<node> v[max_n]; inline bool operator > (const node &x,const node &y)//重载运算符以在小根堆中比较 { return x.b>y.b; } priority_queue<node,vector<node>,greater<node> > q; int p[max_n],id[max_n]; set<int> s; set<int>::iterator it; int main() { int n; scanf("%d",&n); for(int i=1;i<=n;++i) { scanf("%d%d",&c[i].a,&c[i].b); c[i].id=i; v[c[i].a].push_back(c[i]);//把 c[i] 丢进左端点编号对应的 vector 中 } for(int i=1;i<=n;++i) { for(int j=0;j<int(v[i].size());++j) q.push(v[i][j]);//先加入 id[i]=q.top().id;//再取出 p[id[i]]=i; q.pop(); } for(int x=1;x<=n;++x) { for(int j=0;j<int(v[x].size());++j) s.insert(p[v[x][j].id]);//先加入 int i=id[x];//枚举 x 获得对应 i it=s.upper_bound(x);//再查询 if(it!=s.end()&&*it<=c[i].b) { int j=id[*it]; puts("NO"); for(int k=1;k<=n;++k) printf("%d%c",p[k],k<n?' ':'\n'); swap(p[i],p[j]); for(int k=1;k<=n;++k) printf("%d%c",p[k],k<n?' ':'\n'); return 0; }//查询方法:二分找到大于 p[i] 的第一个数,判断其是否小于等于 b[i] } puts("YES"); for(int k=1;k<=n;++k) printf("%d%c",p[k],k<n?' ':'\n'); return 0; } ```