牛客网暑期ACM多校训练营(第四场)J. Hash Function [线段树+拓扑排序]

题意

给你一个对a数组hash之后的hash表,求原来的a数组字典序最小的解。

题解

对于hash表中当前这个数hi,若hi%n!=i的话,说明当前这个数是后移过的,那么说明在hi后移的这一段数必须在hi之前放入才能让hi在i这个位置,所以这就是一个拓扑排序。假如我们暴力建图,那么肯定会TLE。所以我们用线段树优化建图,对于当前这个点,他位移的那段区间,找到在线段树上的映射,将这些子区间的点连接到当前节点,然后将当前节点连向包含这个节点的所有线段树上的区间。对于这张图跑拓扑排序,因此总点数为n*4,边数为O(n*log(n)),时间复杂度为O(n+n*log(n))。

AC代码

#include<stdio.h>
#include<vector>
#include<queue>
#include<string.h>
#define N 200005
using namespace std;
struct node
{
    int num;
    node(){}
    node(int num)
    {
        this->num=num;
    }
};
priority_queue<node>que;
vector<int>vt[N*4],ans;
int a[N],n;
int treeid[N*4],pos[N*4],du[N*4],s[N*4];
bool operator<(node A,node B)
{
    return a[treeid[A.num]]>a[treeid[B.num]];
}
void build(int L,int R,int root)
{
    vt[root].clear(); 
    treeid[root]=n+1;
    du[root]=0;
    if(L==R)
    {
        treeid[root]=L;
        pos[L]=root;
        return ;
    }
    int mid=L+R>>1;
    build(L,mid,root<<1);
    build(mid+1,R,root<<1|1);
}
void update(int L,int R,int l,int r,int root,int who)
{
    if(l<=L&&R<=r)
    {
        du[who]++;
        vt[root].push_back(who);
        return ;
    }
    int mid=L+R>>1;
    if(l<=mid)update(L,mid,l,r,root<<1,who);
    if(r>mid)update(mid+1,R,l,r,root<<1|1,who);
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        ans.clear(); 
        int sum=0,flag=0; 
        scanf("%d",&n);
        a[n+1]=-1;
        build(0,n-1,1);
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
            s[i+1]=s[i]+(a[i]!=-1);
        }
        for(int i=0;i<n;i++)
        {
            if(a[i]==-1)continue;
            sum++;
            if(a[i]%n!=i)
            {
                if(a[i]%n<i)
                {
                    if(s[i]-s[a[i]%n]!=i-a[i]%n)flag=1;
                    update(0,n-1,a[i]%n,i-1,1,pos[i]);
                }
                else 
                {
                    if(s[n]-s[a[i]%n]!=n-a[i]%n)flag=1;
                    update(0,n-1,a[i]%n,n-1,1,pos[i]);
                    if(i)
                    {
                        if(s[i]!=i)flag=1;
                        update(0,n-1,0,i-1,1,pos[i]);
                    }
                }
            }
            int now=pos[i]/2;
            while(now)
            {
                du[now]++;
                vt[pos[i]].push_back(now);
                now/=2;
            }
        }
        if(flag)
        {
            printf("-1\n");
            continue;
        }
        for(int i=0;i<n;i++)
            if(a[i]!=-1&&du[pos[i]]==0)
                que.push(node(pos[i]));
        while(!que.empty())
        {
            node k=que.top();
            que.pop();
            if(a[treeid[k.num]]!=-1)ans.push_back(a[treeid[k.num]]);
            for(int i=0;i<vt[k.num].size();i++)
            {
                int to=vt[k.num][i];
                du[to]--;
                if(du[to]==0)que.push(node(to));
            }
        }
        if(sum!=ans.size())printf("-1\n");
        else 
        {
            for(int i=0;i<sum-1;i++)
                printf("%d ",ans[i]);
            if(sum!=0)printf("%d",ans[sum-1]);
            printf("\n");
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值