把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ2109】【BZOJ2535】[NOI2010] 航空管制(拓扑反向建图)

点此看题面

大致题意:\(n\)个航班,每个航班有个最大起飞序号,且有若干对关系要求某航班在另一航班之前起飞。求任意一个可行的起飞序列,并求出每个航班可能的最小起飞序号。

前言

本以为是道三倍经验,后来发现之所以会有这两道正是因为原先的那道【BZOJ2008】[NOI2010] 航空管制它没有\(SPJ\)。。。

做完这题,也算是学了一个新套路吧。感觉应该挺实用的,甚至感觉好像之前某场模拟赛遇到过这种套路的题目结果没做出来。

然而,写个拓扑都能献上满满的\(bug\),我也真是醉了。。。

反向建图

这道题用到的是拓扑排序的一个套路:反向建图。

考虑正向建图和反向建图配上一个优先队列有何区别。

正向建图:求最大/最小字典序。

反向建图:使较小/较大的数尽量靠前。

因此,这道题显然是需要反向建图的,而这样只要随便一遍拓扑就能解决掉第一个问题。

第二个问题

第二个问题每次要求让某一个航班尽量靠前。

则我们可以在反向拓扑时刚遇到这个航班时先把它拦下来(?),然后等到不得不让它走时(即队列中没有其他航班,或者队列中最大的最大起飞序号都小于当前序号)再放行。

于是第二个问题也迎刃而解了。

所以,事实上,只要了解了反向建图的套路,这道题就非常水了。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 2000
#define M 10000
#define add(x,y) (e[++ee].nxt=lnk[x],++deg[e[lnk[x]=ee].to=y])
using namespace std;
int n,m,a[N+5],q[N+5],ee,lnk[N+5],deg[N+5];struct edge {int to,nxt;}e[M+5];
struct data
{
	int p,t;I data(CI a=0,CI b=0):p(a),t(b){}
	I bool operator < (Con data& o) Con {return t<o.t;}
}s[N+5];priority_queue<data> Q;
I int Topo(CI x)//拓扑
{
	RI i,f=0,T=n,res;data k;for(i=1;i<=n;++i) !deg[i]&&(Q.push(s[i]),0);//初始化队列
	W(!Q.empty()||f)
	{
		if(Q.empty()||Q.top().t<T) res=T,k=s[x],f=0;//不得不放行
		else if(k=Q.top(),Q.pop(),k.p==x) {f=1;continue;}//先拦下来
		for(i=lnk[q[T--]=k.p];i;i=e[i].nxt) !--deg[e[i].to]&&(Q.push(s[e[i].to]),0);//拓扑日常操作
	}
	for(i=1;i<=ee;++i) ++deg[e[i].to];return res;//把deg加回来,因为要拓扑多次
}
int main()
{
	RI i,x,y;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%d",&a[i]),s[i]=data(i,a[i]);
	for(i=1;i<=m;++i) scanf("%d%d",&x,&y),add(y,x);//反向建图
	for(x=Topo(1),i=1;i<=n;++i) printf("%d%c",q[i]," \n"[i==n]);//随便搞一个合法序列,则顺便求出1的答案
	for(printf("%d ",x),i=2;i<=n;++i) printf("%d%c",Topo(i)," \n"[i==n]);return 0;//求出2~N的答案
}
posted @ 2020-05-14 13:37  TheLostWeak  阅读(121)  评论(0编辑  收藏  举报