题干:
知名美食家小 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 #include2 #include 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 }3 #include 4 #include 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 i) dfs(zzyy[i]);
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
2 #include
3 #include
4 #include
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 }
附 toposort 原理图: