1 条题解
-
0
题解
题意简述
给定排列 和部分确定的排列 ( 表示未知),要求构造一个排列 ,满足:
- 与 一致( 时 );
- 是“好的”,即存在一系列题目所述操作(选择区间 满足 ,并将该区间向右循环移位一次),使得 可以变为 。
若可能则输出 ,否则输出 。
思路分析
1. 好排列的充要条件
关键观察:
操作的本质:选择 且 ,将 移动到 的位置,其他元素右移一位。
这个操作不会将原本 中的顺序对 变成逆序对(即如果 在 中出现在 之前,那么操作后 仍在 之前,或者可能 移动到 前面?需要更严谨)。实际上,该操作对于值的大小关系保持如下性质:
- 若 且在 中 出现在 之前,那么在任何可达的排列中, 仍出现在 之前。
证明:每次操作将区间的最小值 移到最左端,它不会越过比它大的元素,也不会被比它小的元素越过(因为它是最小值)。因此,任意两个数之间的相对顺序(按原 中的位置关系)不会改变,除非它们相等(不可能)。
因此, 是好的当且仅当对于任意 ,若 在 中的位置 < 在 中的位置,则 在 中的位置 < 在 中的位置。
即 与 之间的值顺序关系保持一致( 给出了一个值的偏序, 必须线性扩展这个偏序)。
2. 转化为区间限制
设 为 在 中的位置, 为 在 中的位置(若 已知则 中该值的位置,否则未知)。
条件等价于:对所有 ,如果 ,则必须 。这类似于:将值 按照 的顺序排成一列,则 必须保持这个顺序(即 是 的一个线性扩展)。
换句话说,如果我们按 递增的顺序考虑 ,则 也必须递增。于是问题变为:已知某些 的值,其他未知,要求 严格递增(按 顺序)且 每个位置恰好一个值。
3. 检查已知约束
我们按 递增的顺序遍历 ,维护当前已填的 的最大值。
若遇到已知 ,它必须大于之前所有已知 ,否则无解。设 和 为 在 中可能的位置范围:
- :由于所有小于 且 的值必须排在 之前, 的位置至少是这些值的数量 。这可以通过对 顺序做前缀计数得到。
- :由于所有大于 且 的值必须排在 之后, 的位置至多是 减去这些值的数量。也可通过后缀计算。
实际上标程用两个树状数组求 和 :
- 从左到右扫描 (按 的位置),遇到已知 则更新树状数组记录最大值,遇到未知则 当前最大值 作为左界。
- 从右到左扫描,类似得到右界。若左界 > 右界则无解。
4. 贪心填充未知位置
现在问题变成:有 个未知值,每个未知值 有一个可选位置区间 ,且这些区间满足单调性(因为按 顺序 均递增)。
我们要给每个未知值分配一个位置,使得:- 位置互不相同;
- 每个位置 ()上最多一个值;
- 已知值已固定在 中,不能与未知冲突。
经典贪心:按位置 依次考虑,维护一个优先队列,存放所有左界 的未知值,按右界从小到大排序。
对于位置 ,如果 已知,则直接填;否则从优先队列中取出右界最小的(即最紧迫的)赋值给 ,若其右界 则无解。由于区间单调,每次加入的新值左界递增,且右界也递增,因此贪心正确。
5. 实现细节
- 用两个树状数组:一个维护前缀最大值(求 ),一个维护后缀最小值(求 )。
- 将未知值按 分组存入
vc[L]。 - 遍历位置 ,先将所有 的未知值加入优先队列(按 小优先)。
- 若 已知,直接跳过;否则从队列中取出 最小的赋值,若 则无解。
- 输出 。
复杂度
- 树状数组操作 。
- 每个未知值入队出队一次,。
- 总复杂度 , 总和 ,可行。
代码
#include <bits/stdc++.h> using namespace std; const int maxn = 500100; int n, a[maxn], b[maxn], c[maxn], p[maxn], q[maxn]; struct node { int r, x; node(int a = 0, int b = 0) : r(a), x(b) {} bool operator < (const node &o) const { return r > o.r || (r == o.r && x > o.x); } }; vector<node> vc[maxn]; struct DS1 { // 前缀最大值 int c[maxn]; void init() { for (int i=0;i<=n;++i) c[i]=0; } void upd(int x,int d) { for(;x<=n;x+=x&-x) c[x]=max(c[x],d); } int qry(int x) { int r=0; for(;x;x-=x&-x) r=max(r,c[x]); return r; } } T1; struct DS2 { // 后缀最小值 int c[maxn]; void init() { for (int i=0;i<=n;++i) c[i]=n+1; } void upd(int x,int d) { for(;x;x-=x&-x) c[x]=min(c[x],d); } int qry(int x) { int r=n+1; for(;x<=n;x+=x&-x) r=min(r,c[x]); return r; } } T2; void solve() { cin>>n; for(int i=1;i<=n;++i) cin>>a[i], p[a[i]]=i, q[i]=0, vc[i].clear(); for(int i=1;i<=n;++i) { cin>>b[i]; if(b[i]) q[b[i]]=i; } // 检查已知顺序 T1.init(); for(int i=1;i<=n;++i) if(q[i]) { if(T1.qry(p[i])>q[i]) {cout<<"-1\n";return;} T1.upd(p[i],q[i]); } // 求左界 T1.init(); T2.init(); for(int i=1;i<=n;++i) { if(q[a[i]]) T1.upd(a[i],q[a[i]]); else c[i]=T1.qry(a[i])+1; } // 求右界 for(int i=n;i>=1;--i) { if(q[a[i]]) T2.upd(a[i],q[a[i]]); else { int r=T2.qry(a[i])-1; if(c[i]>r) {cout<<"-1\n";return;} vc[c[i]].emplace_back(r,a[i]); } } // 贪心填未知 priority_queue<node> pq; for(int i=1;i<=n;++i) { for(node u:vc[i]) pq.push(u); if(!b[i]) { if(pq.empty()||pq.top().r<i) {cout<<"-1\n";return;} b[i]=pq.top().x; pq.pop(); } } for(int i=1;i<=n;++i) cout<<b[i]<<" \n"[i==n]; } int main() { ios::sync_with_stdio(0); cin.tie(0); int T; cin>>T; while(T--) solve(); return 0; }
- 1
信息
- ID
- 7074
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者