POJ 3687 Labeling Balls(拓扑排序)
http://poj.org/problem?id=3687
题意:给你N个球,编号从1到N.且这N个球的重量各不相同,且他们的重量正好是从1到N个单位.现在还给出了M个关系,每个关系描述了两个不同编号球之间的重量关系.现在要你输出这N个球可能的重量,从1号球到N号球.如果存在多解,则输出1号球最轻的解.如果1号球最轻时依然有多解,那么输出2号球最轻的解.依此类推.
分析:
其实这道题目我们可以理解成有1到N个孔,我们要把N个球放进去.如果2号球房间了5号孔,那么2号球重5个单位.现在我们要找出一个放球的方法,使得球序列满足M条约束且球序列的字典序最小.(球序列的字典序最小就保证了1号球最轻,2号球最轻等等)
以上分析是错误的.字典序最小的序列,并不能保证1号球最轻.
比如下面实例:
1
6 4
1 6
3 1
2 4
4 5
输出应为:23 1 4 5 6
拓扑排序字典序最小的序列是:2 3 1 4 5 6,转换成重量输出结果是3 1 2 4 5 6. 其中字典序最小的序列使得1号球的重量变成了3.
但是正确结果的拓扑排序应该是:3 1 2 4 5 6,转换成重量是:2 3 1 4 5 6.
这个序列使得1号球的重量变成了2,明显这个序列更优,但是字典序不一定最小.
其实上面的错误解法的思想是我们每次都优先选入度为0的最小序号的球放当前最小可用的格子.这样不能保证得到的解是1号球最轻的.比如23<1我们第一步肯定先放2,然后放3,再是1.但其实我们可以先放3,再1,再2的.这样1号球只重2个单位.
但是如果我们每次都是优先选出度为0的最大序号球放在当前可能的最大格子里,那么我们得到的解能保证1号球是最轻的.因为当我们想放1号球的时候,还剩下没放的球比如都是比1号球要轻的(就算它们编号比1大).如果某个球与1号球没关系,那么它肯定已经被放置在了一个大的格子里.
总结上述原理就是:我们如果要放i号球的时候,我们一定要保证当前还没有被放的所有球中,只有明确比i号球轻的球或是序号<i的球.如果与i号球无关的球且序号>i的话,那么明显我应该先放这个大序号的球到大编号格子上去.
AC代码:
#include<cstdio> #include<cstring> using namespace std; const int maxn=200+10; int n,m; int G[maxn][maxn]; int out[maxn];//出度 int ans[maxn];//ans[i]=x表示i号球重x int main() { int T; scanf("%d",&T); while(T--) { memset(G,0,sizeof(G)); memset(out,0,sizeof(out)); scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int u,v; scanf("%d%d",&u,&v);//从u->v的边,说明v比u重 if(G[u][v]==0) { G[u][v]=1; out[u]++; } } int i,j; for(i=n;i>=1;i--)//从n号格子开始放球 { for(j=n;j>=1;j--)//优先放编号大的球 if(out[j]==0)//出度为0,表示没有比j还重的球没放了 { out[j]--;//标记 ans[j]=i; for(int k=1;k<=n;k++)if(G[k][j])//减少与j先关的所有点的出度 out[k]--; break; } if(j<1) break;//无出度为0的球了 } if(i>=1)//还有格子没放球,说明有环 printf("-1\n"); else { printf("%d",ans[1]); for(int i=2;i<=n;i++) printf(" %d",ans[i]); puts(""); } } return 0; }