传送门:https://www.nowcoder.com/acm/contest/142/J
题目大意就是给你一个散列表,还原出字典序最小的原序列
解法:通过推样例发现,一个数x
如果不在x%n
的位置,那么从x%n
到当前数字位置i-1
的区间内都已经被占满,那么考虑从x%n
到i-1
所有的点到i
建一条边,然后跑一次拓扑排序(这里建边相当于是限制第i个数字一定比x%n
到i-1
的数字后出现)。然后发现这样子建边是o(n^2)的。于是采用线段树优化建边。
线段树建边的过程:在build函数中,让儿子节点与父亲节点连边,在update函数中,让区间对目标点建边,这样就可以保证l-r区间内的所有点到目标点都有了一条间接的边。(除开根节点的其他点都可以当做一个虚点)
#include
using namespace std;
const int maxn=2e5+5;
const int inf=-0x3f3f3f3f;
int a[maxn];
int n,maxx;
int id[maxn],val[maxn<<2],indeg[maxn<<2];
vector<int>e[maxn<<2],ans;
priority_queueint,int>,vectorint ,int>>,greaterint,int>>>q;
void addedge(int x,int y)
{
e[x].push_back(y);
indeg[y]++;
}
void build(int rt,int L,int R)
{
maxx=max(maxx,rt);
val[rt]=inf;
if(L==R){
id[L]=rt;
val[rt]=a[L];
}else{
int mid=L+R>>1;
build(rt<<1,L,mid);
build(rt<<1|1,mid+1,R);
addedge(rt<<1,rt);
addedge(rt<<1|1,rt);
}
}
void update(int rt,int L,int R,int l,int r,int u)
{
if(l>r) return;
if(l<=L&&R<=r) addedge(rt,u);
else{
int mid=L+R>>1;
if(l<=mid) update(rt<<1,L,mid,l,r,u);
if(r>mid) update(rt<<1|1,mid+1,R,l,r,u);
}
}
int main()
{
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
int T;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i=0;iscanf("%d",&a[i]);
build(1,0,n-1);
int cnt=0;
for(int i=0;iif(a[i]==-1) continue;
cnt++;
int t=a[i]%n;
if(i==t) q.push({val[id[i]],id[i]});
else if(i>t) update(1,0,n-1,t,i-1,id[i]);
else{
update(1,0,n-1,t,n-1,id[i]);
update(1,0,n-1,0,i-1,id[i]);
}
}
while(!q.empty()){
pair<int,int>temp=q.top();
q.pop();
int u=temp.second;
if(temp.first!=inf) ans.push_back(temp.first);
for(auto v:e[u]){
indeg[v]--;
if(indeg[v]==0) q.push({val[v],v});
}
}
if(ans.size()!=cnt) puts("-1");
else if(cnt==0) puts("");
else{
for(auto it:ans) cout<' ';cout<for(int i=0;i<=maxx;i++) e[i].clear(),indeg[i]=0;
ans.clear();
maxx=0;
while(!q.empty()) q.pop();
}
return 0;
}