【学术篇】2.28测试T2 线段 拓扑排序

题目:


思路:
看到这种找前后的题目... 第一反应就是拓扑排序_(:з」∠)_
每条线段都有左右两个端点咯, 然后就乱搞吧..
我们用\(i\)\(i'\)分别表示第\(i\)条线段的左右端点..
然后如果\(x\)\(y\)的左边, 那么\(x'\)一定小于\(y\), 我们就建一条\(x'->y\)的边
如果\(x\)\(y\)相交, 那么一定\(x<y'\)\(y<x'\)(显然), 我们就分别建\(x->y'\)\(y->x'\)两条边.
然后对于每个点\(x\), \(x<x'\), 那么再建\(x->x'\)的边...
然后为了字典序, 把编号扔进一个小根堆里维护一下, 按照堆的顺序跑一边拓扑排序大约就可以了吧..
Wrong的情况也很好特判, 只要判断进队的点的个数不到\(2n\)就好了...
轻松加愉快地过了样例. 于是这样就做完了?


然后非常无耻地找了一下数据一测, 发现得到了10pts... 就是输出Wrong的10分...
然后发现顺序全错了...这就很尴尬...
那就打开组数据看一下嘛, 反正\(n\leq10\)的数据很适合手玩..
然后第二组数据(\(n\)最小的一组)是这样的:

7 4
2 1 7
2 2 7
1 7 5
2 4 2

我们来画一下:
就可以看出问题了. 如果我们这么做, \(x\)进堆之前, 若\(\exists\)\(y->x\)\(\exists u满足x<u<y\), 则拓扑序列将变为\(u y x\)而不是\(y x u\), 字典序就不优了..(看清楚题目的字典序指的是什么..)
那怎么办啊? 为了解决这个问题, 我们考虑把边都反向, 堆变成一个大根堆, 然后顺序从后向前做(就是最后把拓扑序列倒过来), 就可以了..
因为我们发现, 这么一搞, 存在依赖的编号较小的点将会比较靠后的弹出, 倒过来就变成优先了, 也就满足了字典序的要求..
然后想到这一点就没什么难度了...
std好像用了手写堆, 但是我人懒就直接上priority_queue咯, 不过跑的还是飞快, 最大的点也用不到0.3s..
代码:

#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int N=202020;
priority_queue<int> q;
inline int gn(int a=0,char c=0){
	for(;c<'0'||c>'9';c=getchar());
	for(;c>47&&c<58;c=getchar())a=a*10+c-'0';
return a;}
struct edge{int to,next;}e[N];
int v[N],du[N],ans[N],tot,cnt,n,m;
inline void buildedge(int x,int y){
	++du[y]; e[++tot].to=y; e[tot].next=v[x]; v[x]=tot;
}
int main(){
	freopen("seg.in","r",stdin); freopen("seg.out","w",stdout);
	n=gn(),m=gn();
	for(int i=1;i<=n;++i) buildedge(i*2,i*2-1);
	for(int i=1;i<=m;++i){
		int x=gn(),y=gn(),z=gn();
		if(x==1) buildedge(z*2,y*2-1),buildedge(y*2,z*2-1);
		else buildedge(z*2-1,y*2);
	}n<<=1;
	for(int i=1;i<=n;++i)
		if(!du[i]) q.push(i); cnt=n;
	while(!q.empty()){
		int x=q.top(); q.pop();
		for(int j=v[x];j;j=e[j].next){
			--du[e[j].to];
			if(du[e[j].to]==0)
				q.push(e[j].to);
		}
		ans[x]=cnt--;
	}
	if(cnt) puts("Wrong");
	else for(int i=1;i<=n;i+=2)
		printf("%d %d\n",ans[i],ans[i+1]);
}
posted @ 2018-02-28 15:56  Enzymii  阅读(143)  评论(0编辑  收藏  举报