zoukankan      html  css  js  c++  java
  • 【做题记录】[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) 的个数。

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

  • 相关阅读:
    免费的编程中文书籍索引 from github
    win7 Python 环境 准备 配置
    SQL Server 2008 允许远程链接,适用于广域网和局域网
    CTP API开发期货自动交易平台概论
    一步一步重写 CodeIgniter 框架 (4) —— load_class 管理多个对象实例的思路
    一步一步重写 CodeIgniter 框架 (3) —— 用面向对象重构代码
    一步一步重写 CodeIgniter 框架 (2) —— 实现简单的路由功能
    一步一步重写 CodeIgniter 框架 (1) —— url 如何映射到具体的方法
    一步一步重写 CodeIgniter 框架 -- 原因和思路
    GDI双缓冲绘图
  • 原文地址:https://www.cnblogs.com/mk-oi/p/14476837.html
Copyright © 2011-2022 走看看