题目大意:
有一种hash方式为,当a[i]%n产生冲突时,就往后推到一位没有冲突的地方。现在给你hash后的序列,能否推出hash前的顺序,如果可以,输出最小字典序。
解题思路:
刚开始比赛的时候想的是拓扑排序,因为对于拓扑排序来说在A事件没有发生前,是不可能发生B事件的。那么就是说如果我a[i]%n的结果是pos 那么就可以对于 pos 到 i 所有的数对于a[i]连一条有向边,这样就表示在这些数被选之前,a[i]这个数不可能被选,其实完美符合拓扑排序的定义,但是极限情况下的边数为n^2条,同时这道题目n=2e6,因此必须优化,其实当时比赛的时候也想到了 就是 对于 A->B B->C 是没有必要连A->C这条边的。但是根本不知道该怎么优化这条边,然后就gaygay了。比赛结束后发现很多队其实是用一种很巧妙的方法乱怼过去的。。。
这里说一种做法,万恶的csdn,写到一半崩了,结果就没草稿了,搞得不想写了。。。
首先预处理将所有正确位置的数加入到优先队列中,当我们从队列中拿出一个数的时候,分别记录两个变量l=(pos-1)%n,r=(pos+1)%n,之后判断 l r 两个位置的数是否之前已经被取出作为答案,如果是的话,就不断的左移右移直到找到一个没有被作为答案的 l 和 r 。之后的操作就直接判断 a[r]%n 这个位置的数是否已经被作为答案取出去了,如果是,就将a[r]加入优先队列中即可。
Ac代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;
int n,tot,a[maxn],nex[maxn],pre[maxn];
vector<int> ans;
bool in[maxn],vis[maxn];
void init()
{
ans.clear();tot=0;
for(int i=0;i<=n;i++) vis[i]=0;
for(int i=0;i<=n;i++) in[i]=0;
for(int i=0;i<=n;i++) nex[i]=pre[i]=0;
}
int main()
{
int QAQ;
scanf("%d",&QAQ);
while(QAQ--)
{
scanf("%d",&n);
init();
priority_queue< pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>> > que;
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
if(a[i]==-1) continue;
if(a[i]%n==i)
{
in[i]=1; //在队列中的数
que.push({a[i],i});
}
tot++; //记录不为-1的数的个数
}
while(!que.empty())
{
int now=que.top().second; //从队列中拿出一个数
que.pop(); vis[now]=1;
ans.push_back(a[now]);
if(ans.size()==tot) break; //如果不为-1 的数取完了 break
int r=(now+1)%n,l=(n+now-1)%n;
while(vis[r]) r=nex[r]; //判断 l r 是否都被取过
while(vis[l]) l=pre[l];
nex[now]=r,pre[now]=l; //记录位置 方便跳跃
if(!in[r]&&a[r]!=-1&&(n+r-l-1)%n>=((n+r-a[r]%n)%n)) //判断a[r]的正确位置的数是否已经被取走
{
que.push({a[r],r});
in[r]=1;
}
}
if(tot!=ans.size()) printf("-1\n");
else if(tot==0) printf("\n");
else
{
for(int i=0;i<(int)ans.size();i++)
printf("%d%c",ans[i],i==(int)ans.size()-1?'\n':' ');
}
}
return 0;
}