D. Permutation Addicts(图论/bfs)

题目
参考

题意

给定长度为n的数组a和整数k,0<=k<=n。根据以下规则计算数组b
对于1<=i<=n,令x=a[i]
如果x<=k,令b[x]为最后一个元素a[j] (1<=j<i) ,使得a[j]>k,如果不存在该a[j],则令b[x]=n+1;
如果x>k,令b[x]为最后一个元素a[j] (1<=j<i),使得a[j]<=k,如果不存在该a[j],则令b[x]=0。

现在已知b数组,求k和a数组。如果有多个a满足题意,输出任意一个。

思路

k的计算
  • 对于i<=k,有b[i]>i;
  • 对于i>k,有b[i]<i

根据上述,我们找到最大的下标i,使得b[i]>i,则k为该i

建图G

对于每个1<=i=n,建有向边b[i]到i。

对于该图,我们定义点集0到k为A,点k+1到n+1为点集B。
那么每个有向边要么从A到B,要么从B到A。

性质1:顶点0和顶点n+!有且仅有一个是孤立点。

证明:

首先,顶点0和顶点n+1不能全是孤立点,因为根据定义,b[a1],要么为0,要么为n+1。

假设点0和点n+1全不是孤立点。那么存在下标x和y使得b[x]=0,b[y]=n+1,且有1<=y<=k<x<=n,找到点i,j使得a[i]=x,a[j]=y

  • 如果i<j,则a[i]=x>k>=a[j]=y,根据b[y]的定义,a[i]=x是它的一个候选点,因此b[y]!=n+1

  • 如果i>j,则a[j]=y<=k<a[i]=x,根据b[x]的定义,a[j]=y是它的一个候选点,因此b[x]!=0

因此,点0和点n+1有且仅有一个是孤立点。

性质2:图G不含环,即图G是一个DAG

证明:图不存在包含0和n+1的环,因为不存在b[i]->i,i的取值为1到n。因此,我们只需要考虑1到n的点。

对于图G的每条边(u,v),1<=u,v<=n,它的含义为,在a数组中,u出现在v的前面。因为a是一个排列,所有点都出现且仅出现一次。有环时,说明存在u,v,使得u在v的前面,同时v在u的前面。这显然不可能。

因此,根据性质1和性质2,图G是一个以0或n+1为根的树。

性质3:对于图G上的每个点u,最多有一个孩子节点v,其叶子节点非空。

证明:假设u存在两个叶子节点非空的孩子节点v1,v2。令w1,w2分别为v1和v2的孩子节点。

定义a’[x]为x的下标,使得a[i]=x。根据这个定义,我们有a’[u]<a’[v],对于树上的每条边(u,v)。不失一般性,我们假设a’[v1]<a’[v2]

那么,我们有a’[v2]<a’[w1],否则a’[v2]>a’[w1],那么w1是b[v2]的候选点,u则不再是v2的父亲节点。
因此,a’[v2]<a’[w1],这意味着v2是b[w1]的候选点,这使得v1不是w1的父亲节点。
综上,矛盾,得证。

BFS求a

根据上述性质,我们按bfs序,遍历树G,并让叶子节点优先遍历。
如果同一层级存在多个叶子节点,则按任意顺序遍历。

通过以上方式,就可以得到我们想要的a。

代码

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pcc pair<char, char>
#define inf 0x3f3f3f3f
const int maxn = 200010;

int n;
void solve() {
    scanf("%d", &n);
    vector<int> b(n + 1);
    vector<vector<int> > v(n + 2);
    int k = 0;
    for (int i = 1; i <= n; ++i) {
    	scanf("%d", &b[i]);
    	if (b[i] > i) {
    		k = i;
		}
		v[b[i]].push_back(i);
	}
	int rt = v[0].size() ? 0 : n + 1;
	
	vector<int> q = {rt};
	for (int i = 0; i < q.size(); ++i) {
		int u = q[i];
		sort(v[u].begin(), v[u].end(), [&](int a, int b) {
			return v[a].size() < v[b].size();
		});
		for (auto x: v[u]) {
			q.push_back(x);
		}
	}
	if (q.size() != n + 1) {
		printf("-1");
		return;
	}
	printf("%d\n", k);
	for (int i = 1; i < n; ++i) {
		printf("%d ", q[i]);
	}
	printf("%d\n", q[n]);
}

int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
    	solve();
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值