cf1158 C. Permutation recovery
题意:
有一个未知的排列。\(ne_i\) 表示 \(i\) 的右边第一个大于 \(a_i\) 的数的位置,若不存在则 \(ne_i=n+1\)。
现只知道一部分 \(ne\),\(ne_i=-1\) 表示不知道。请构造原排列
思路:
法一:思维、构造
若 \(i < j\and ne_i \neq j\),则 \(a_i>a_j\)。此时要求 \(ne_j\le ne_i\),否则无解
其他情况一定有解:
对 \(ne_i=-1\),不妨令 \(ne_i=i+1\)
每个 \(ne_i\) 向 \(i\) 连边,则所有边不相交。然后在得到的森林上从 n+1 号节点开始 bfs 构造答案,要求父比子大,且同属一父的左儿子比右边大
懒得想怎么判断无解了,直接单调栈验证吧!
const signed N = 5 + 5e5;
int n, ne[N], ans[N]; vector<int> G[N];
void sol() {
cin >> n; for(int i = 1; i <= n; i++) cin >> ne[i];
for(int i = 2; i <= n+1; i++) G[i].clear();
for(int i = 1; i <= n; i++) //ne[i]向i连边
G[~ne[i] ? ne[i] : i+1].push_back(i);
queue<int> q; q.push(n+1);
int idx = n;
while(q.size()) {
int u = q.front(); q.pop();
for(int v : G[u]) ans[v] = idx--, q.push(v);
}
//单调栈模拟,验证答案
stack<int> stk; stk.push(n+1), ans[n+1] = INF;
for(int i = n; i; i--) {
while(stk.size() && ans[stk.top()] < ans[i]) stk.pop();
if(~ne[i] && stk.top() != ne[i]) return cout << -1, void();
stk.push(i);
}
for(int i = 1; i <= n; i++) cout << ans[i] << ' ';
}
法二:线段树优化建图+拓扑排序
大数向小数连边:\(ne_i\) 向 \(i\) 连边、\(i\) 向 \((i+1,ne_i)\) 中的所有点连边
只需要一棵线段树(即所谓“入树”)
const signed N = 5 + 5e5;
vector<int> G[N*4];
int id[N]; // id[i]为点i在线段树中的点编号
int in[N*4], ord[N*4]; //入度,拓扑序
#define mid (l+r)/2
#define lson u*2
#define rson u*2+1
#define ls lson,l,mid
#define rs rson,mid+1,r
void build(int u, int l, int r) {
G[u].clear(); ord[u] = 0; //顺便初始化
if(l == r) id[l] = u;
else {
G[u].pb(lson), G[u].pb(rson), in[lson] = in[rson] = 1;
build(ls), build(rs);
}
}
void Link(int u, int l, int r, int x, int y, int p) { //p向[x,y]连边
if(x <= l && r <= y) G[id[p]].pb(u), in[u]++;
else {
if(x <= mid) Link(ls, x, y, p);
if(y > mid) Link(rs, x, y, p);
}
}
void topo() {
queue<int> q; q.push(1);
int idx = 0;
while(q.size()) {
int u = q.front(); q.pop();
ord[u] = ++idx; //访问顺序
for(int v : G[u]) if(--in[v] == 0) q.push(v);
}
}
int p[N], ans[N]; //算答案
void sol() {
int n; cin >> n;
build(1,1,n+1); //注意是n+1
for(int i = 1; i <= n; i++) {
int ne; cin >> ne;
if(~ne) G[id[ne]].pb(id[i]), in[id[i]]++;
if(i+1<ne) Link(1,1,n+1, i+1, ne-1, i); //注意是n+1
}
topo();
for(int i = 1; i <= n; i++)
if(!ord[id[i]]) return cout << "-1\n", void();
iota(p+1, p+1+n, 1); //用拓扑序算答案。映射有点绕
sort(p+1, p+1+n, [](int a, int b) {
return ord[id[a]] > ord[id[b]];
});
for(int i = 1; i <= n; i++) ans[p[i]] = i;
for(int i = 1; i <= n; i++) cout << ans[i] << "\n "[i<n];
}

浙公网安备 33010602011771号