【做题记录】[USACO07MAR] Ranking the Cows G

Problem

洛谷P2881
\(n\) 个数,给出 \(m\) 个形如 \(a>b\) 的关系,问还要调查多少关系才能确定所有数的大小关系。

Solution

这题应该有两个方法,floyd 和拓扑排序。这里重点讲拓扑排序。

首先要明确一点,如果没有给任何关系,那么我们要做的就是每两个调查一下关系,那么需要调查的总次数就是

\[(n-1)+(n-2)+\cdots+3+2+1=\frac{n(n-1)}{2} \]

那么,如果有一些关系了呢?我们就不需要调查这些关系了,把已知关系的数量减去,就是现在要调查的关系。
那为啥结果不是 \(\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]=truef[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]=truef[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\) 的个数。

如果你觉得这篇题解帮到了你,就点个赞吧,比心ღ

posted @ 2021-03-03 20:49  Mine_King  阅读(120)  评论(0编辑  收藏  举报