vijos P1790拓扑编号

描述

H国有n个城市,城市与城市之间有m条单向道路,满足任何城市不能通过某条路径回到自己。

现在国王想给城市重新编号,令第i个城市的新的编号为a[i],满足所有城市的新的编号都互不相同,并且编号为[1,n]之间的整数。国王认为一个编号方案是优美的当且仅当对于任意的两个城市i,j,如果i能够到达j,那么a[i]应当<a[j]。

优美的编号方案有很多种,国王希望使1号城市的编号尽可能小,在此前提下,使得2号城市的编号尽可能小...依此类推。

格式

输入格式

第一行读入n,m,表示n个城市,m条有向路径。

接下来读入m行,每行两个整数:x,y
表示第x个城市到第y个城市有一条有向路径。

输出格式

输出一行:n个整数
第i个整数表示第i个城市的新编号a[i],输出应保证是一个关于1到n的排列。

样例1

样例输入1[复制]

5 4
4 1
1 3
5 3
2 5

样例输出1[复制]

2 3 5 1 4

限制

每个测试点1s

提示

30%的测试点满足:n <= 10, m <= 10
70%的测试点满足:n <= 1000, m <= 10000
100%的测试点满足:n <= 100000, m <= 200000
输入数据可能有重边,可能不连通,但保证是有向无环图。

来源

Topcode

分析:拓扑排序+堆优化

从前向后找每次找到序号最小的入度为0的点?样例就不对。(脑补一下为什么。。。)

从后向前反向贪心?可行。每次找出度为0的编号最大的点,删反向边,减出度,并令这个点的编号当前最大。

一个有向无环图中出度为0的点一定在最后,那么根据序号大小从大到小编号,保证小编号一定留给序号小的且尽量靠前的点。


Code:


program t;
var n,m,i,x,y,l,j,p:longint;
    head,c,ans:array[1..100000]of longint;
    edge:array[1..200000,1..2]of longint;
begin
 read(n,m);
 for i:=1 to n do head[i]:=-1;
 for i:=1 to m do
  begin
   read(x,y);
   l:=l+1;
   edge[l,1]:=x;
   edge[l,2]:=head[y];
   head[y]:=l;
   c[x]:=c[x]+1;
  end;
 for i:=n downto 1 do
  begin
   j:=n;
   while (j>0)and(c[j]<>0) do j:=j-1;
   ans[j]:=i;
   c[j]:=n+1;
   p:=head[j];
   while p<>-1 do
    begin
     c[edge[p,1]]:=c[edge[p,1]]-1;
     p:=edge[p,2];
    end;
  end;
 for i:=1 to n do write(ans[i],' ');
end.

但这题的数据范围是100000拓扑排序复杂度O(n^2)

在每次找出度为0的点时用堆优化,复杂度O(nlogn)

Code:

program tt;
var n,m,i,x,y,l,j,p:longint;
    head,c,ans,heap:array[1..100000]of longint;
    edge:array[1..200000,1..2]of longint;
procedure add(x:longint);
var i,j,t:longint;
begin
 heap[l]:=x;
 i:=l;
 while i>1 do 
  begin
   j:=i shr 1;
   if heap[j]<heap[i] then 
    begin
	 t:=heap[j];heap[j]:=heap[i];heap[i]:=t;
	 i:=j;
	end
   else
    break;
  end;
end;
procedure updata;
var i,j,t:longint;
begin
 i:=1;
 while i shl 1<=l do
  begin
   j:=i shl 1;
   if (j<l)and(heap[j]<heap[j+1]) then j:=j+1;
   if heap[i]<heap[j] then 
    begin
	 t:=heap[i];heap[i]:=heap[j];heap[j]:=t;
	 i:=j;
	end
   else
    break;
  end;
end;
begin
 read(n,m);
 for i:=1 to n do head[i]:=-1;
 for i:=1 to m do
  begin
   read(x,y);
   l:=l+1;
   edge[l,1]:=x;
   edge[l,2]:=head[y];
   head[y]:=l;
   c[x]:=c[x]+1;
  end;
 for i:=1 to n do 
  if c[i]=0 then 
   begin 
	l:=l+1;
	add(i);
   end;
 for i:=n downto 1 do
  begin
   j:=heap[1];//每次加入度为0的点,用大根堆维护编号
   ans[j]:=i;
   c[j]:=n+1;
   heap[1]:=heap[l];l:=l-1;updata;
   p:=head[j];
   while p<>-1 do
    begin
     c[edge[p,1]]:=c[edge[p,1]]-1;
	 if c[edge[p,1]]=0 then begin l:=l+1;add(edge[p,1]);end;
     p:=edge[p,2];
    end;
  end;
 for i:=1 to n do write(ans[i],' ');
end.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值