题外话:
这道题其实已经想到正解了,但是在维护答案的细节上出了问题
看来还是考虑的不全面,不能从特殊情况推出一般的规律
还是不能偷懒,一定要用多样化的数据测试自己算法
也算是一个教训吧,dada们比你高明到不知哪里去了,力求完美吧
分析:
直觉是一个图论
- x x 与相交: a[x]<b[y] a [ x ] < b [ y ] 且 a[y]>b[x] a [ y ] > b [ x ]
- x x 在左边: b[x]<a[y] b [ x ] < a [ y ]
- 线段 x x 满足
这相当于一个拓扑排序问题,小的数相当于安排在前面的任务
同时我们要保证字典序最小:
a[1]尽可能小说明任务1要尽可能早做,b[1]尽可能小说明任务2要尽可能早做……
那么我们可以直接建出DAG,top的时候优先选择编号小的点?
这样WA的就太冤了
看一个非常简单的例子:
4 1
2 4 2
建出的图长这样:
如果用上述的贪心方法,我们得到的答案是这样的:
1 2
7 8
3 4
5 6
但是这并不是字典序最小的答案
正确答案:
1 2
5 6
7 8
3 4
我们可以把3号线段往后挪,使4号线段提前,这样被4号线段约束的2号线段也可以提前
怎么破?
把DAG 反向建边得到新图
在新图中求字典序最大的拓扑排序,再将这个排序反序输出就是满足要求的答案
尝试着解释一下:
在DAG中,存在一些编号较大但是拓扑序靠前的点,也存在编号较小但是受约束而拓扑序靠后的点
如果我们正序进行,那些拓扑序靠前但是编号较大的点会受编号的限制而排在后面,从而影响之后的答案
如果逆序进行,拓扑序靠前靠且编号大的点会尽量靠后,反过来之后就会尽量靠前了
或者说,那些编号较小但是拓扑序较大的点,在逆序的时候回尽量靠后,这样反过来就可以满足:拓扑序虽然大,但是由于编号小,尽可能靠前安排
tip
注意相交时的连边:
a[x]−>b[y],b[x]−>a[y]
a
[
x
]
−
>
b
[
y
]
,
b
[
x
]
−
>
a
[
y
]
这样连边包含了所有情况,其他连边方式都不全面
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
const int N=100005;
int n,m;
int du[N<<1];
struct node{
int y,nxt;
};
node way[N<<1];
int ans[N][2],st[N<<1],tot=0;
priority_queue<int> Q;
void add(int u,int w) {
tot++;way[tot].y=w;way[tot].nxt=st[u];st[u]=tot;
}
void solve() {
for (int i=1;i<=2*n;i++)
if (du[i]==0) Q.push(i);
int cnt=n*2;
while (!Q.empty()) {
int now=Q.top(); Q.pop();
ans[(now+1)/2][(now&1)? 0:1]=cnt--;
for (int i=st[now];i;i=way[i].nxt) {
du[way[i].y]--;
if (du[way[i].y]==0) Q.push(way[i].y);
}
}
if (cnt!=0) {
printf("Wrong\n");
return;
}
for (int i=1;i<=n;i++)
printf("%d %d\n",ans[i][0],ans[i][1]);
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) { //反向连边
add(i*2,i*2-1);
du[i*2-1]++;
}
int opt,x,y;
for (int i=1;i<=m;i++) {
scanf("%d%d%d",&opt,&x,&y);
if (opt==1) {
add(y*2,x*2-1); du[x*2-1]++;
add(x*2,y*2-1); du[y*2-1]++;
} else {
add(y*2-1,x*2);
du[x*2]++;
}
}
solve();
return 0;
}