拓扑排序——洛谷 P2881 [USACO07MAR]排名的牛Ranking the Cows 题解

想找原题请点击这里:传送门

在看原题前感谢whx大佬的博客让我搞明白了这道题,所以这道题中大部分思路可能与此博客题解相同。

原题:

题目描述
Each of Farmer John's N cows (1 ≤ N ≤ 1,000) produces milk at a different positive rate, and FJ would like to order his cows according to these rates from the fastest milk producer to the slowest.

FJ has already compared the milk output rate for M (1 ≤ M ≤ 10,000) pairs of cows. He wants to make a list of C additional pairs of cows such that, if he now compares those C pairs, he will definitely be able to deduce the correct ordering of all N cows. Please help him determine the minimum value of C for which such a list is possible.

FJ想按照奶牛产奶的能力给她们排序。现在已知有N头奶牛(1 ≤ N ≤ 1,000)。FJ通过比较,已经知道了M(1 ≤ M ≤ 10,000)对相对关系。每一对关系表示为“X Y”,意指X的产奶能力强于Y。现在FJ想要知道,他至少还要调查多少对关系才能完成整个排序。

输入格式
Line 1: Two space-separated integers: N and M

Lines 2..M+1: Two space-separated integers, respectively: X and Y. Both X and Y are in the range 1...N and describe a comparison where cow X was ranked higher than cow Y.

输出格式
Line 1: A single integer that is the minimum value of C.

输入输出样例
输入 #1复制
5 5
2 1
1 5
2 3
1 4
3 4
输出 #1复制
3
说明/提示
From the information in the 5 test results, Farmer John knows that since cow 2 > cow 1 > cow 5 and cow 2 > cow 3 > cow 4, cow 2 has the highest rank. 
However, he needs to know whether cow 1 > cow 3 to determine the cow with the second highest rank.
Also, he will need one more question to determine the ordering between cow 4 and cow 5.
After that, he will need to know if cow 5 > cow 3 if cow 1 has higher rank than cow 3.
He will have to ask three questions in order to be sure he has the rankings: "Is cow 1 > cow 3? Is cow 4 > cow 5? Is cow 5 > cow 3?"

首先一看这题想到上一篇题解都是跟牛有关都是考虑在一个图中一个点的排名能否确定的问题。不同的是,这道题目中n<=1000。而时限为1s,(10^3)^3=1e9,肯定是要超出时限的所以不能使用floyd来AC。

(虽然网上有的题解使用了bitset来使floyd AC了emmm但这就有点类似开挂了,但ROS一会儿简单讲一下。bitset有空专门讲一下)

那么来分析一下这道题:

先延续上一道题讲点基础。

  • 首先我们还是创建一个布尔型的f二位数组来储存一头牛能否赢另一头牛(f[i][j]==1表示i比j强)
  • 然后floyd就是把所有的中间点都当成中转站:如果i能到j,j能到k,那么i就能到k。
  • 然后如果说f[i][j]和f[j][i]都是false,它的意思不是i和j都比对方弱(真是见了鬼了)。而是指这二者之间的关系还未确定。反之f[i][j]||f[j][i]为true则二者之间的关系是确定的。如果i与所有j关系都确定则i的排名也就确定了(即上一道题所述)

这道题与上一道题的最大不同点就是这一道题需要判断的是还至少需要判断几次才能使所有牛的排名全部明确。

首先ROS在分析这道题之前先分析一下“至少”这个词是什么意思。

“至少”在字典中的解释为:表示最小的限度。但奈何我中华汉字文化博大精深,其实至少这个词有时表示“最少“,有时表示”最多“。而在这里的意思是”在次数最少的情况下最多需要多少次就可以使所有排名明确“。即我们要确保不管我的数据是什么,我进行这么多次比较一定可以使所有牛的排名明确。

好的字典翻完了现在接着分析题。

那么如果说是”至少“(前面已经翻完字典了所以后面我就用至少了不再解释了)需要多少,那么用floyd最简单的思路:用floyd算法处理完之后,进行O(n²)的比较操作如果两个点之间没有强弱的比较那么就说明这两点之间需要比较,然后对于所有两点间没有进行过强弱比较的点全部都是ans++。这里要注意一点的是我们访问过两个点时,便要让f[i][j]=true,防止我们之后访问时重复添加。

50分代码:

50分代码本尊:

 1 #include<bits/stdc++.h>
 2 #define N 1005
 3 #define M 4505
 4 using namespace std;
 5 int n,m; 
 6 bool f[N][N];
 7 int a,b;
 8 int ans;
 9 int main(){
10     scanf("%d%d",&n,&m);
11     for(int i=1;i<=m;i++){
12         scanf("%d%d",&a,&b);
13         f[a][b]=true;
14     }
15     for(int i=1;i<=n;i++){
16         for(int j=1;j<=n;j++){
17             for(int k=1;k<=n;k++){
18                 if(j==k) continue;
19                 if(i==j||i==k) continue;
20                 f[j][k] = f[j][k] || (f[j][i]&&f[i][k]);
21             }
22         }
23     }
24     for(int i=1;i<=n;i++){
25         for(int j=1;j<=n;j++){
26             if(i==j) continue;
27             if(!(f[i][j]||f[j][i])) ans++,f[i][j]=true;
28         }
29     }
30     printf("%d",ans);
31     return 0;
32 }

好的那么开始优化这个代码。

我一开始的话没有学会拓扑排序相关知识所以ROS便在网上找到了用bitset优化floyd算法的时间复杂度。

什么是bitset?

bitset存储二进制数位。

bitset就像一个bool类型的数组一样,但是有空间优化——bitset中的一个元素一般只占1 bit,相当于一个char元素所占空间的八分之一。

bitset中的每个元素都能单独被访问,例如对于一个叫做foo的bitset,表达式foo[3]访问了它的第4个元素,就像数组一样。

bitset有一个特性:整数类型和布尔数组都能转化成bitset。

bitset的大小在编译时就需要确定。如果你想要不确定长度的bitset,请使用(奇葩的)vector<bool>。

在此感谢yxd学姐的整理

正所谓bitset是”高端压位卡常题必备STL“。

但这道不需要卡常的题却硬生生有人用了解决卡常熟的解决方法:使用了bitset来解决。

主要步骤跟上面思想差不多,代码一会儿贴。这里ROS就讲一下为什么用bitset一定会更快。

虽然说对于一个bitset整体进行操作复杂度还是n,所以乍一看这个操作的时间复杂度貌似并没有减少而在n很小的情况下反倒增加了许多。但是我们对bitset进行运算时,w位的电脑能够一下子处理w位的bitset(也就是二进制)所以说整体的时间复杂度就是O(n³/w).32位的电脑就正好卡在比1s少一点的时限下了。是不是很神奇!!!

还有就是如果两个不同的点之间一一对比那么能够形成的组数即为C(2,n)=n*(n-1)/2。所以我们在如下的代码中要用n*(n-1)/2-ans(因为ans储存的是确定的组数)

代码张贴在此:

 1 #include<bits/stdc++.h>
 2 #define N 1001
 3 using namespace std;
 4 int n,m;
 5 int a,b;
 6 bitset <N> f[N];
 7 bool flag=true;
 8 int ans;
 9 int read(){            //快读 
10     int x=0;
11     char tmp;
12     tmp=getchar();
13     while(tmp!=' '&&tmp!='\n'){
14         x*=10;
15         x+=tmp-'0';
16         tmp=getchar();
17     }
18     return x;
19 }
20 int main(){
21     n=read();
22     m=read();
23     for(int i=1;i<=m;i++){
24         a=read();
25         b=read();
26         f[a][b]=1;
27     }
28     for(int i=1;i<=n;i++){
29         for(int j=1;j<=n;j++){
30             if(f[j][i]){
31                 f[j]|=f[i];
32             }
33         }
34     }
35     for(int i=1;i<=n;i++){
36         ans+=f[i].count();
37     }
38     printf("%d",n*(n-1)/2-ans);
39     return 0;
40 }

好的为了使floyd能够使用连解决卡常熟问题的看家本领都拿出来了。那ROS还是讲讲正常思路吧。

说实话一开始ROS由于没有学过拓扑排序便在弄这种奇奇怪怪的东西。

后来还是决定接触一下拓扑排序吧。

拓扑排序过程很简单:

  • 我们在构建了一个图时同时记录每个点的入度,之后从头到尾一个个端点访问,如果断电的入度为0则将该端点加入队列中。
  • 临时存放队头后弹出队头,然后运用链式前向星访问弹出的队头的每一条边,并且将边问边的终点的入度-1。在一条条边访问的时候进行需要的操作(如果需要拓扑排序则将弹出的边加入队列;本题中则要将胜负状态转移),然后如果访问的边的终点的入度为0,则将终点加入队列中。
  • 进行以上操作直到队列为空

然后我们计算f[i][j]和f[j][i]均为false的情况(同时不要忘记在计算时将f[i][j]=true防止重复计数)

AC代码如下:

 1 #include<bits/stdc++.h>
 2 #define N 1001
 3 #define M 10001
 4 using namespace std;
 5 int n,m; 
 6 int x,y;
 7 int ans;
 8 int degree[N];
 9 int head[N];
10 int tot;
11 bool f[N][N],vis[N];
12 struct tree{
13     int to;
14     int nxt;
15 }e[M];
16 void add(int a,int b){
17     e[++tot].to=b;
18     e[tot].nxt=head[a];
19     head[a]=tot;
20 }
21 int read(){
22     int t=0;
23     char tmp;
24     tmp=getchar();
25     while(tmp!=' '&&tmp!='\n'){
26         t*=10;
27         t+=tmp-'0';
28         tmp=getchar();
29     }
30     return t;
31 }
32 void bfs(){
33     queue <int> q;
34     int tmp;
35     for(int i=1;i<=n;i++) if(degree[i]==0) q.push(i);
36     while(!q.empty()){
37         tmp=q.front();q.pop();
38         vis[tmp]=true;
39         for(int i=head[tmp];i;i=e[i].nxt){
40             int v=e[i].to;
41             if(vis[v]) continue;
42             for(int j=1;j<=n;j++) if(f[j][tmp]) f[j][v]=true;
43             degree[v]--;
44             if(degree[v]==0) q.push(v);
45         }
46     }
47     return ;
48 }            
49 int main(){
50     n=read();m=read();
51     for(int i=1;i<=m;i++){
52         x=read();y=read();
53         add(x,y); 
54         f[x][y]=true;
55         degree[y]++; 
56     }
57     bfs();
58     for(int i=1;i<=n;i++){
59         for(int j=1;j<=n;j++){
60             if(i==j) continue;
61             if(!f[i][j]&&!f[j][i]){
62                 ans++;
63                 f[i][j]=true;
64             }
65         }
66     }
67     printf("%d",ans);
68     return 0;
69 }

猜你喜欢

转载自www.cnblogs.com/robertspot/p/12380375.html