zoukankan      html  css  js  c++  java
  • [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 原理图:

    越努力 越幸运
  • 相关阅读:
    TCP四种定时器--学习笔记
    Python魔术师--self
    python的socket里 gethostbyname 与 gethostbyname_ex 的区别
    用python查看URL编码的中文
    基于linux 的2048
    用灵活的指针访问类私有变量
    ie8无法拉伸背景图
    图片的onerror 事件解析
    stream.js
    Promise
  • 原文地址:https://www.cnblogs.com/OI-zzyy/p/11172093.html
Copyright © 2011-2022 走看看