【生日前夕的胡策】训练2.28 T2(拓扑)

本文探讨了如何利用拓扑排序解决线段相交问题,并通过优化字典序来确保答案的合理性。介绍了使用大根堆和反向边求解字典序最大拓扑排序的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

T2

这里写图片描述

题解:

x与y相交说明a[x] < b[y]且a[y] < b[x]
x在y左边说明b[x] < a[y]
每条线段x还应满足a[x] < b[x]
这相当于一个拓扑排序问题,小的数相当于安排在前面的任务
输出的第i个数就是第i个任务,那么a[1]尽可能小说明任务1要尽可能早做,b[1]尽可能小说明任务2要尽可能早做……
方法就是把DAG反向建边得到新图,在新图中求字典序最大的拓扑排序,再将这个排序反序输出就是满足要求的答案

然而在考场上我首先写出了一个小根堆,建立正向边,求字典序最小的拓扑排序,过了样例,然而我随便的一组小数据就挂掉了

4 1
2 4 2
建的图是这样的
这里写图片描述
根据我们次次找最小的原理,得到的答案是这样的
1 2
7 8
3 4
5 6

这样字典序最小吗?然而字典序最小的是这样的
1 2
5 6
7 8
3 4
为什么会这样,因为有一些编号小但受约束所以拓扑序靠后的点。
怎么解决?其实只需要大根堆,建立反向边,求字典序最大的拓扑排序
为什么呢?
在DAG中,存在一些编号较大但是拓扑序靠前的点,也存在编号较小但是受约束而拓扑序靠后的点
如果我们正序进行,那些拓扑序靠前但是编号较大的点会受编号的限制而排在后面,从而影响之后的答案
如果逆序进行,拓扑序靠前且编号大的点会尽量靠后,反过来之后就会尽量靠前了
或者说,那些编号较小但是拓扑序较大的点,在逆序的时候会尽量靠后,这样反过来就可以满足:拓扑序虽然大,但是由于编号小,尽可能靠前安排

代码:

#include <cstdio>
#include <iostream>
using namespace std;
const int N=100005;
struct hh{int a,b;}se[N];
int q[N*2],cnt; 
void push(int x)
{
    q[++cnt]=x;
    int fa=cnt>>1,now=cnt;
    while (fa&&q[fa]<q[now])
    {
        swap(q[fa],q[now]);
        now=fa; fa>>=1;
    }
}
void del()
{
    q[1]=q[cnt--];
    int now=1,l=now<<1,r=now<<1|1,better=l;
    if (r<=cnt && q[r]>q[l]) better=r; 
    while (better<=cnt && q[now]<q[better])
    {
        swap(q[now],q[better]);
        now=better; l=now<<1; r=now<<1|1;
        better=l;
        if (r<=cnt && q[r]>q[l]) better=r; 
    }
}
int ans[N*2],tot,nxt[N*2],point[N*2],v[N*2],du[N*2];bool vis[N*2];
void addline(int x,int y){++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;}
int main()
{
    freopen("seg.in","r",stdin);
    freopen("seg.out","w",stdout);
    int n,m;
    scanf("%d%d",&n,&m);
    int num=0;
    for (int i=1;i<=n;i++) se[i].a=++num,se[i].b=++num;
    for (int i=1;i<=m;i++)
    {
        int id,x,y;
        scanf("%d%d%d",&id,&x,&y);
        if (id==1) du[se[y].a]++,addline(se[x].b,se[y].a),du[se[x].a]++,addline(se[y].b,se[x].a);
        else du[se[x].b]++,addline(se[y].a,se[x].b);
    }
    for (int i=1;i<=n;i++) du[se[i].a]++,addline(se[i].b,se[i].a);

    for (int i=1;i<=n*2;i++)
      if (!du[i]) push(i);

    for (int i=n*2;i>=1;i--)
    {
        if (!cnt) {printf("Wrong\n");return 0;}
        int now=q[1]; del(); ans[now]=i;
        for (int j=point[now];j;j=nxt[j])
        {
            du[v[j]]--;
            if (!du[v[j]]) push(v[j]);
        }
    }   
    for (int i=1;i<=n*2;i+=2) printf("%d %d\n",ans[i],ans[i+1]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值