Problem
洛谷P2881
有 (n) 个数,给出 (m) 个形如 (a>b) 的关系,问还要调查多少关系才能确定所有数的大小关系。
Solution
这题应该有两个方法,floyd 和拓扑排序。这里重点讲拓扑排序。
首先要明确一点,如果没有给任何关系,那么我们要做的就是每两个调查一下关系,那么需要调查的总次数就是
那么,如果有一些关系了呢?我们就不需要调查这些关系了,把已知关系的数量减去,就是现在要调查的关系。
那为啥结果不是 (frac{n(n-1)}{2}-m) 呢?因为关系有传递性,也就是说,(a>b,b>c) 可以得出 (a>c),可以分析出三条关系,但实际给出的只有两条关系。
接下来的问题就是求我们能分析出的关系了。
我们把关系画成一张图,(u>v) 就连一条 (u) 到 (v) 的边,这样,两点可以确定关系就是其中一点能到达另一点。
Floyd做法
众所周知,Floyd 是用来求全源最短路——也就是任意两点的最短路径。那么我们把 Floyd 数组的定义改一下:(dis_{u,v}=1/0) 表示 (u) 能否到 (v)。我们发现原来的松弛仍然适用,适用的原因还是关系有传递性。
于是就可以很快写出代码:
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
f[i][j]=f[i][j]|(f[i][k]&f[k][j]);
最后若f[i][j]=true
或f[j][i]=true
则答案 (-1)。
先别急!分析一下代码,发现时间复杂度是 (O(n^3)) 的,但 (n le 1000),跑不过去。
拓扑排序做法
还是根据关系有传递性这一性质,我们发现,我们可以按照拓扑序去更新能到达这个点的点,只要在找到一条边时把前面点能到达的点都加到后面的点上即可。(听起来可能有点拗口,仔细理解一下?)
关于拓扑排序的可行性,显然不会出现环,因为环意味着 (a>b,b>c,c>a),这显然是不可能的。
于是又能写出以下代码:
while(!q.empty())
{
int x=q.front();
for(int i=1;i<=n;i++)
if(g[x][i])//表示x到i有边
{
for(int j=1;j<=n;j++)
if(f[x][j]) f[i][j]=true//f[a][b]=true表示能分析出a>b的关系
if(!--in[i]) q.push(i);
}
}
最后若f[i][j]=true
或f[j][i]=true
则答案 (-1)。
如果用链式前向星存图,可以做到 (O(n(n+m))),可通过此题。
bitset 优化复杂度
但是但是,我想用 Floyd 怎么办?我拓扑排序想用邻接矩阵存图怎么办?
满足你!
先讲讲 bitset 是个肾么东西。
定义一个 bitset:
bitset<大小>变量名
然后可以把它当一个 bool 数组使用:
bitset<100>a;
a[5]=true;
a[98]=false;
都是可以的。
那它比 bool 数组好在哪里?支持位运算!
我们还可以把 bitset 当成一个数来看,那么它内部的每一个元素都是二进制的一位。
像这样:
bitset<3>a,b;
a[0]=0,a[1]=1,a[2]=1;
b[0]=1,b[1]=0,b[2]=1;
a^=b
然后a
就变成了:
a[0]:1
a[1]:1
a[2]:0
所以 bitset可以做到区间赋值。接下来就要用这个特性优化两个算法。
对 Floyd 的优化
我们只要枚举中转点和终点,然后对于终点,能走到它的,能走到中转点的都能走到它,那么只要把它们合并一下。
思考一下,(a) 有则有,(b) 有则有,都没有则没有,这对应着什么操作?按位或!
于是可以写出以下代码:
for(int k=1;k<=n;k++)
for(int j=1;j<=n;j++)
if(g[k][j])//要确保它们有边。
f[j]|=f[k];//合并信息
最后求答案只要减去所有的f[i]
即可。
哦对了,a.count()
是询问 bitset 中 (1) 的个数。
对拓扑排序的优化
已经有了上面Floyd的经验,那么这也很好想,也把赋值信息改成按位或就行了。
while(!q.empty())
{
int x=q.front();
q.pop();
for(int i=1;i<=n;i++)
if(g[x][i])
{
f[i]|=f[x];
if(!--in[i]) q.push(i);
}
}
最后求答案只要减去所有的f[i]
即可。
a.count()
是询问 bitset 中 (1) 的个数。
如果你觉得这篇题解帮到了你,就点个赞吧,比心ღ