[HNOI2015]菜肴制作(toposort + 单队)

题干:

  知名美食家小 A 被邀请至 ATM 大酒店,为其品评菜肴。ATM 酒店为小 A 准备了 N 道菜肴,酒店按照为菜肴预估的质量从高到低给予 1 到 N 的顺序编号,预估质量最高的菜肴编号为 1。由于菜肴之间口味搭配的问题,某些菜肴必须在另一些菜肴之前制作,具体的,一共有 M 条形如「i 号菜肴『必须』先于 j 号菜肴制作”的限制」,我们将这样的限制简写为 ⟨i,j⟩。
  现在,酒店希望能求出一个最优的菜肴的制作顺序,使得小 A 能尽量先吃到质量高的菜肴:也就是说,
  在满足所有限制的前提下,1 号菜肴「尽量」优先制作;
  在满足所有限制,1 号菜肴「尽量」优先制作的前提下,2 号菜肴「尽量」优先制作;
  在满足所有限制,1 号和 2 号菜肴「尽量」优先的前提下,3 号菜肴「尽量」优先制作;
  在满足所有限制,1 号和 2 号和 3 号菜肴「尽量」优先的前提下,4 号菜肴「尽量」优先制作;

  以此类推。
  例一:共四道菜肴,两条限制 ⟨3,1⟩、⟨4,1⟩,那么制作顺序是 3,4,1,2。
  例二:共五道菜肴,两条限制 ⟨5,2⟩、⟨4,3⟩,那么制作顺序是 1,5,2,4,3。
  例一里,首先考虑 1,因为有限制 ⟨3,1⟩ 和 ⟨4,1⟩,所以只有制作完 3 和 4 后才能制作 1,而根据(3),3 号又应「尽量」比 4 号优先,所以当前可确定前三道菜的制作顺序是 3,4,1;接下来考虑 2,确定最终的制作顺序是 3,4,1,2。
  例二里,首先制作 1 是不违背限制的;接下来考虑 2 时有 ⟨5,2⟩ 的限制,所以接下来先制作 5 再制作 2;接下来考虑 3 时有 ⟨4,3⟩ 的限制,所以接下来先制作 4 再制作 3,从而最终的顺序是 1,5,2,4,3。
  现在你需要求出这个最优的菜肴制作顺序。无解输出“Impossible!” (不含引号,首字母大写,其余字母小写)

题解:

  本题就是让输出一个序列,比较容易的就容易想到dfs或bfs(在正解中dfs不可行)。

15%:

  题干中已经说的比较明白,需要判环。判环时就用 toposort 判断一下是否所有点都可以入队。如果有点未入队,那么就一定有环;如果全都入队,则从1~n枚举一下,不断找最小的菜肴先做。这种方法实际得分15分,附上代码有兴趣的可以看看。。。

Code:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<vector>
 5 #define $ 100010
 6 using namespace std;
 7 int m,n,t,first[$],tot,sta[$],out[$],b[$],vis[$];
 8 struct tree{    int to,next;    }a[$],ready[$];
 9 inline void add(int x,int y){
10     a[++tot]=(tree){    y,first[x]    };
11     first[x]=tot; 
12     ++out[y];
13 }
14 inline bool cmp(tree x,tree y){
15     if(x.to==y.to) return x.next<y.next;
16     return x.to<y.to;
17 }
18 inline bool judge(){
19     memset(sta,0,sizeof(sta));
20     int up=1,down=0,ans=0;
21     for(register int i=1;i<=n;++i)
22         if(!out[i]) sta[++down]=i;
23     while(up<=down){
24         ans++;
25         int x=sta[up++];
26         for(register int i=first[x];i;i=a[i].next){
27             int to=a[i].to;
28              out[to]--;
29              if(out[to]==0) sta[++down]=to;
30         }
31     }
32     if(ans!=n) return 1;
33     return 0;
34 }
35 inline void dfs(int x){
36     if(vis[x]) return;
37     vector<int> zzyy;
38     for(register int i=first[x];i;i=a[i].next){
39         int to=a[i].to;
40         if(vis[to]) continue;
41         zzyy.push_back(to);
42     }
43     if(!zzyy.size()){    printf("%d ",x); vis[x]=1;  return;    }  
44     sort(zzyy.begin(),zzyy.end());
45     for(register int i=0;i<zzyy.size();++i) dfs(zzyy[i]);
46     printf("%d ",x); vis[x]=1;
47 }        
48 inline void work(){
49     memset(a,0,sizeof(a)); tot=0;
50     memset(first,0,sizeof(first));
51     memset(out,0,sizeof(out));
52     memset(ready,0,sizeof(ready));
53     memset(vis,0,sizeof(vis));
54     scanf("%d%d",&n,&m);
55     for(register int i=1;i<=m;++i) 
56         scanf("%d%d",&ready[i].to,&ready[i].next);
57     sort(ready+1,ready+m+1,cmp);
58     for(register int i=1;i<=m;++i)
59         if(ready[i].to!=ready[i-1].to||ready[i].next!=ready[i-1].next)
60             add(ready[i].next,ready[i].to);
61     if(judge()){    puts("Impossible!"); return;    }
62     for(register int i=1;i<=n;++i) if(!vis[i]) dfs(i);
63     puts("");
64 }
65 signed main(){
66     scanf("%d",&t);
67     while(t--) work();
68 }
View Code

100%:

  作者其实一度认为上面提到的一定是正解。。。但为什么只拿到15分呢?有一组数据以供参考:

1     5 4      3 4     4 1     2 5     5 1

  正解输出为 2 3 4 5 1;  而15%输出为 3 4 2 5 1

  其实这体现出了dfs不可处理后效性的问题。在样例中,dfs会先判断与1相连的4、5之间的大小,并选择4先进行dfs,这与提干要求的不符(果断抛弃)。。。

  dfs不行我们可以试试bfs(toposort)。

  正解利用了单调队列的的特性,即最上面的永远是现阶段的最大值(或最小值),这恰好符合题干要求。我们可以建立一个大根堆(之所以不再用小根堆,是因为在dfs中就相当于一个小根堆,并不能得到正确答案;大根堆之所以可以,是因为在实现过程中,我们建的是反图(dfs中也是)),将入度为0的节点先放入单队中,进行 toposort,期间记录一下出队顺序(也就是拓扑序),最终倒序输出即可。

  针对为什么既要建反图,又要跑大根堆

  举个例子如图:

 

  如果你用优先队列拓扑排序得到的是:3 5 6 4 1 7 8 9 2 0 。

  但是正确答案为 6 4 1 3 9 2 5 7 8 0 这样使得小的(1)尽量在前面。

  这里我们可以得到 前面的小的不一定排在前面,但是有一点后面大的一定排在后面

  我们看 6和3不一定3排在前面,因为6后面连了一个更小的数字1能使得6更往前排。

  再看 2和 8,8一定排在后面,因为8后面已经没有东西能使它更往前排(除了0)。

  所以最后我们的做法就是 建立一个反图,跑一边字典序最大的拓扑排序,最后再把这个排序倒过来就是答案了。

Code:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<queue>
 5 #define $ 100010
 6 using namespace std;
 7 int m,n,t,first[$],tot,sta[$],out[$],b[$],vis[$];
 8 struct tree{    int to,next;    }a[$],ready[$];
 9 inline void add(int x,int y){
10     a[++tot]=(tree){    y,first[x]    };
11     first[x]=tot; 
12     ++out[y];
13 }
14 inline bool cmp(tree x,tree y){
15     if(x.to==y.to) return x.next<y.next;
16     return x.to<y.to;
17 }
18 inline bool judge(){
19     priority_queue<int> q;
20     int ans=0;
21     for(register int i=1;i<=n;++i) if(!out[i]) q.push(i);
22     while(q.size()){
23         int x=q.top(); q.pop();
24         vis[++ans]=x;
25         for(register int i=first[x];i;i=a[i].next){
26             int to=a[i].to;
27              out[to]--;
28              if(out[to]==0) q.push(to);
29         }
30     }
31     if(ans!=n) return 1;
32     return 0;
33 }        
34 inline void work(){
35     memset(a,0,sizeof(a)); tot=0;
36     memset(first,0,sizeof(first));
37     memset(out,0,sizeof(out));
38     memset(ready,0,sizeof(ready));
39     memset(vis,0,sizeof(vis));
40     scanf("%d%d",&n,&m);
41     for(register int i=1;i<=m;++i) 
42         scanf("%d%d",&ready[i].to,&ready[i].next);
43     sort(ready+1,ready+m+1,cmp);
44     for(register int i=1;i<=m;++i)
45         if(ready[i].to!=ready[i-1].to||ready[i].next!=ready[i-1].next)
46             add(ready[i].next,ready[i].to);
47     if(judge()){    puts("Impossible!"); return;    }
48     for(register int i=n;i>=1;--i) printf("%d ",vis[i]); puts("");
49 }
50 signed main(){
51     scanf("%d",&t);
52     while(t--) work();
53 }
View Code

 附 toposort 原理图:

 

posted @ 2019-07-11 19:21  OI_zzyy  阅读(219)  评论(0编辑  收藏  举报