草庐IT

最大01互斥矩阵/状态压缩

指针常量 2024-01-21 原文

最大01互斥矩阵

目录

1.题目
2.算法分析
3.算法实现

  ~  

1.题目:

题目描述

给定 1 1 1 1000 1000 1000 行× 20 20 20 列的 01 01 01 矩阵,对于该矩阵的任意 1 1 1 列,其中值为 1 1 1 的元素的数量不超过 10 10% 10.

设有两个非空集合 A A A B B B,每个集合由矩阵的若干列组成.集合 A A A B B B 互斥是指对于矩阵的任意一行,同时满足下列 2 2 2个条件:

( 1 ) (1) (1) A A A中有一个或多个元素在这一行上的值是 1 1 1,则 B B B中的元素在这一行全部是 0 0 0;

( 2 ) (2) (2) B B B中有一个或多个元素在这一行上的值是 1 1 1,则 A A A中的元素在这一行全部是 0 0 0.

请你设计一个算法,找出一对互斥集合 A A A B B B,使得 A A A B B B包含的列的总数最大.

输入格式

输入 1000 1000 1000行× 20 20 20列的 01 01 01矩阵.

输出格式

每组输出两行,使得 A A A B B B的列数和最大.

第一行输出 A A A 集合中的所有列的编号(下标从 0 0 0开始),以空格分开;

第二行输出 B B B集合中的所有编号,格式同上.

如果没有找到非空集合 A A A B B B,则输出两行空行.

为保证输出唯一,每个集合的输出按照升序排列,如果存在并列的情况,则采用以下策略:

A A A B B B元素列数差的绝对值最小;

A A A的列数要大于 B B B的列数 ;

A A A的元素和要小于 B B B的元素和.

2.算法分析

(1)状态压缩【二进制搜索】

考虑用二进制对每一个状态进行表示,此即状态压缩

对于 A A A而言,假设我们选择了第 0 , 1 , 2 , 3 0,1,2,3 0,1,2,3列,可以使用二进制数表示为: 000...001111 000...001111 000...001111

其中,假设最低位是第 0 0 0位,那么最高位为 19 19 19

这个数第 k k k位上如果为 1 1 1,代表 A A A选择了第 k k k列,否则未选择第 k k k列,

可以把 A A A矩阵对列的所有选择方案唯一表示一个 20 20 20位的二进制数

基于这个思想, A A A的所有选择方案可以唯一转化为一个十进制数

这个十进制数的取值范围是 0 0 0 2 20 − 1 2^{20}-1 2201

(2)初步设计【会超时】

显然,我们可以把 A , B A,B A,B的选择情况都看成一个 20 20 20位的二进制数

遍历所有情况,判断 A , B A,B A,B是否互斥,并记录最大列数情况下的 A , B A,B A,B

总计算次数大概为: 2 20 × 2 20 = 2 40 ≈ 1 0 12 2^{20} \times 2^{20} = 2^{40}\approx 10^{12} 220×220=2401012(次)

C + + C++ C++ 1 s 1s 1s大概完成 1 0 9 10^9 109次计算,这样的话需要 1 0 3 s ≈ 17 m i n 10^3s \approx 17min 103s17min

这显然是不可取的,故要进行优化设计.

(3)优化思路【预处理+剪枝】

事实上,当我们确定了 A A A的选择方案后,不一定需要遍历 B B B的所有情况

A A A的选择方案确定以后, B B B事实上存在一种最大选择情况

比如, A A A选择第 0 , 1 , 2 , 3 0,1,2,3 0,1,2,3列后,如果第 7 , 8 , 9...19 7,8,9...19 7,8,9...19列都和 A A A的某些列互斥

那么 B B B最多只能选择第 4 , 5 , 6 4,5,6 4,5,6列.

为了快速地在给定 A A A的选择后找到 B B B的最大选择情况,

我们引入预处理数组 c h o i c e [ k ] choice[k] choice[k]

其中 c h o i c e [ t ] choice[t] choice[t]表示当 A A A选择了第 t t t列后 B B B最多能选择哪些列

举例说明如下:

假设第 1 1 1列与第 3 , 4 , 5 , 6...19 3,4,5,6...19 3,4,5,6...19列都矛盾

那么 A A A选择了第 1 1 1列后, B B B最多只能选择第 0 , 2 0,2 0,2

用二进制数作记录, c h o i c e [ 1 ] = 000...00101 choice[1]=000...00101 choice[1]=000...00101

我们如果把这个二进制数化为十进制数,显然取值范围为: 0 0 0 2 20 − 1 2^{20}-1 2201

所以用int显然已经可以满足需要.

引入了 c h o i c e choice choice数组后,快速得到 B B B的最大选择方案可以极大减少搜索次数.

设当前 A A A选择第 0 , 1 0,1 0,1列,

c h o i c e [ 0 ] = 000...01110 , c h o i c e [ 1 ] = 000...0101 choice[0]=000...01110,choice[1]=000...0101 choice[0]=000...01110,choice[1]=000...0101

表示 A A A选择了第 0 0 0列后, B B B最多只能选择第 1 , 2 , 3 1,2,3 1,2,3

A A A选择了第 1 1 1列后, B B B最多只能选择第 0 , 2 0,2 0,2

显然 B B B最多只能选择第 2 2 2

我们对 c h o i c e [ 0 ] choice[0] choice[0] c h o i c e [ 1 ] choice[1] choice[1] & \& &运算即得 B B B的最大选择

000...01110 & 000...0101 = 000...0100 000...01110 \& 000...0101=000...0100 000...01110&000...0101=000...0100

(4)优化算法

0.预处理 c h o i c e choice choice数组

1.遍历 A A A的所有选择情况,即从 0 0 0 2 20 − 1 2^{20}-1 2201

2.根据 c h o i c e choice choice数组得到 B B B的最大选择方案

3.由于我们总是可以使得 A A A的列数不小于 B B B

所以如果当前 A A A的列数小于 B B B就进行下一次循环(剪枝)

否则判断总列数是否大于之前记录的最大列数,若大于则更新.

有相同的列数和时,结合实际情况判断即可

(5)时间复杂度

由于 A A A 20 20 20列可选,至多需要不超过计算 2 20 2^{20} 220级别,也即 1 0 6 10^6 106级别

C + + C++ C++显然是可以在 1 s 1s 1s内跑完的.

3.算法实现

基于以上分析,得到算法实现如下:

#include<iostream>
using namespace std;

const int M = 25, N = 1010;
bool judge[M][M];//记录两列是否矛盾
int a[N][M], choice[M];//a数组记录输入
int sum[M];//存储列的和
int ares = -1, bres = -1;//存储A,B的最优选择
//asum,bsum是当前的A,B的元素和
//eps是列数差的绝对值,tc是当前最大列数和
int asum, bsum, eps = 100, tc;
//判断两列是否互斥
bool dispose(int x, int y)
{
    for (int i = 0; i < 1000; i++)
        if (a[i][x] + a[i][y] == 2)return false;
    return true;
}
//预处理judge[][]数组判断两列是否互斥
void init()
{
    for (int i = 0; i < 20; i++)
        for (int j = i + 1; j < 20; j++)
            if (dispose(i, j))judge[i][j] = judge[j][i] = true;
}
//得到choice[]数组
void get_choice()
{
    for (int i = 0; i < 20; i++)
    {
        int sum = 0;
        for (int j = 0; j < 20; j++)
            if (judge[i][j])sum += 1 << j;
        choice[i] = sum;
    }
}

//lowbit函数
int lowbit(int x)
{
    return x & (-x);
}
//返回二进制中1的个数
int get_one(int x)
{
    int res = 0;
    while (x)x -= lowbit(x), res++;
    return res;
}
//预处理所有列的和
void get_c()
{
    for (int i = 0; i < 20; i++)
    {
        int res = 0;
        for (int j = 0; j < 1000; j++)
            res += a[j][i];
        sum[i] = res;
    }
}

//得到数x二进制表示中所有列的和
int get_sum(int x)
{
    int k = 0, res = 0;
    while (x)
    {
        if (x & 1)res += sum[k];
        k++;
        x >>= 1;
    }
    return res;
}
//输出答案
void get_answer(int x)
{
    int k = 0, len = 20;
    while (len--)
    {
        if (x & 1)
            cout << k << " ";
        k++;
        x >>= 1;
    }
    cout << endl;
}
//同列数和列数差时比较排序次序
//返回true表示x应在y之后
bool compare(int x, int y)
{
    while (x && y)
    {
        if (lowbit(x) == lowbit(y))
        {
            x -= lowbit(x), y -= lowbit(y);
            continue;
        }
        else
        {
            if (lowbit(x) > lowbit(y))return true;
            return false;
        }
    }
    return false;
}
//更新函数
void update(int i, int j)
{
    ares = i, bres = j;
    tc = asum + bsum;
    eps = asum - bsum;
}

int main()
{
    for (int i = 0; i < 1000; i++)
        for (int j = 0; j < 20; j++)
            cin >> a[i][j];

    //预处理choice[]数组,judge[]数组,sum[]数组
    init(), get_choice(), get_c();
    //遍历A的所有选择
    for (int i = 1; i < 1 << 20; i++)
    {
        int temp = i, k = 0, j = -1;//j初始化-1是因为-1是111...111
        while (temp)
        {
            if (temp & 1)//如果这一位是1
                j = (j & choice[k]);//j和这一位的选择作&
            temp >>= 1;
            k++;
        }
        //得到i,j选择的列数
        asum = get_one(i), bsum = get_one(j);

        if (asum >= bsum && bsum)
        {
            if (asum + bsum > tc)//如果列数更大
                update(i, j);
            else if (asum + bsum == tc)//如果列数一样
            {
                //如果列数差的绝对值更小则更新
                if (asum - bsum < eps)
                    update(i, j);
                else if (asum - bsum == eps)//如果列数差相等
                {
                    if (compare(ares, i))
                        update(i, j);
                    else if(get_sum(ares) >= get_sum(bres) && get_sum(i) < get_sum(j))
                        update(i, j);
                }
            }
        }
    }

    if (~ares)//ares!=-1表示存在非空集合,~是取反,~(-1)=0
        get_answer(ares),get_answer(bres);

    else cout << endl << endl;
    return 0;
}

附加说明:

l o w b i t lowbit lowbit函数 l o w b i t ( x ) = x & ( − x ) lowbit(x)=x \& (-x) lowbit(x)=x&(x)

返回二进制表示下数 x x x的最后一位 1 1 1的位置

假设 x > 0 x>0 x>0,那么~ x x x是其按位取反

− x -x x是其相反数,为按位取反再加 1 1 1

x x x的最后 1 1 1 1 1 1按位取反为 0 0 0,再加 1 1 1那么这 1 1 1位就为 1 1 1

如果把 x x x和~ x x x & \& &运算,那么除了最后 1 1 1位1之外其他位置都为 0 0 0

如:假设 x x x是4位有符号数

x = 0110 , − x = 1010 , l o w b i t ( x ) = x & ( − x ) = 0010 x=0110,-x=1010,lowbit(x) = x \& (-x)=0010 x=0110,x=1010,lowbit(x)=x&(x)=0010

c o m p a r e compare compare函数

判断在满足题目所有条件以及排序方式下还存在多个答案的情况下,哪一个答案应该先输出.

如对于 A : 1 , 2 , 3 A:1,2,3 A:1,2,3, B : 4 B:4 B:4 A : 0 , 1 , 2 A:0,1,2 A:0,1,2, B : 3 B:3 B:3

如果这两组解在所有排序方式下都相同,优先输出 A : 0 , 1 , 2 A:0,1,2 A:0,1,2(按照选择的列数大小排序, 0 , 1 , 2 0,1,2 0,1,2 1 , 2 , 3 1,2,3 1,2,3之前,也就是这种情况下不使用 1 , 2 , 3 1,2,3 1,2,3来更新 0 , 1 , 2 0,1,2 0,1,2)

4.数据测试

提供了3组用例供调试使用.

链接:测试用例

有关最大01互斥矩阵/状态压缩的更多相关文章

  1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  2. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  3. ruby-on-rails - 跳过状态机方法的所有验证 - 2

    当我的预订模型通过rake任务在状态机上转换时,我试图找出如何跳过对ActiveRecord对象的特定实例的验证。我想在reservation.close时跳过所有验证!叫做。希望调用reservation.close!(:validate=>false)之类的东西。仅供引用,我们正在使用https://github.com/pluginaweek/state_machine用于状态机。这是我的预订模型的示例。classReservation["requested","negotiating","approved"])}state_machine:initial=>'requested

  4. ruby - 字符串文字中的转义状态作为 `String#tr` 的参数 - 2

    对于作为String#tr参数的单引号字符串文字中反斜杠的转义状态,我觉得有些神秘。你能解释一下下面三个例子之间的对比吗?我特别不明白第二个。为了避免复杂化,我在这里使用了'd',在双引号中转义时不会改变含义("\d"="d")。'\\'.tr('\\','x')#=>"x"'\\'.tr('\\d','x')#=>"\\"'\\'.tr('\\\d','x')#=>"x" 最佳答案 在tr中转义tr的第一个参数非常类似于正则表达式中的括号字符分组。您可以在表达式的开头使用^来否定匹配(替换任何不匹配的内容)并使用例如a-f来匹配一

  5. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  6. 旋转矩阵的几何意义 - 2

    点向量坐标矩阵的几何意义介绍旋转矩阵的几何含义之前,先介绍一下点向量坐标矩阵的几何含义点:在一维空间下就是一个标量,如同一条直线上,以任意某一个位置为0点,以一定的尺度间隔为1,2,3...,相反方向为-1,-2,-3...;如此就形成了一维坐标系,这时候任何一个点都可以用一个数值表示,如点p1=5,即即从原点出发沿着x轴正方向移动5个尺度;点p2=-3,负方向移动3个尺度;     在一维坐标系上过原点做垂直于一维坐标系的直线,则形成了二维坐标系,此时描述一个点需要两个数值来表示点p3=(3,2),即从原点出发沿着x轴正方向移动3个尺度,在此基础上沿着y轴正方向移动两个尺度的位置就是点p3。

  7. ruby-on-rails - 为模型创建状态属性 - 2

    我想为我的Task模型创建一个status属性,该属性将按以下顺序指示它在三部分进度中的位置:打开=>进行中=>完成。它的工作方式类似于亚马逊包裹的交付方式:已订购=>已发货=>已交付。我想知道设置此属性的最佳方法是什么。我可能是错的,但创建三个独立的bool属性似乎有点多余。实现此目标的最佳方法是什么? 最佳答案 Rails4有一个内置的enummacro.它使用单个整数列并映射到键列表。classOrderenumstatus:[:ordered,:shipped,:delivered]end状态映射如下:{ordered:0,

  8. ruby - Ruby 的压缩库? - 2

    是否有任何可用于Ruby的开源压缩/解压库?有没有人实现过LZW?或者,是否有任何使用压缩组件的开源库可以提取出来独立使用?编辑——感谢您的回答!我应该提到我必须压缩的是只驻留在数据库中的长字符串(我不会压缩文件)。此外,如果可以执行此操作的任何库都具有用于客户端压缩/分解的等效JavaScript实现,那将是理想的,因为这将用于Web应用程序。 最佳答案 您会在rubystdlib下找到所有已交付的ruby​​库的一个很好的列表.我会使用zlib库,它是开放的,无处不在,您会发现几乎所有语言的库!

  9. ruby - 是否可以在不实际发送或读取数据的情况下查明 ruby​​ 套接字是否处于 ESTABLISHED 或 CLOSE_WAIT 状态? - 2

    s=Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0)s.connect(Socket.pack_sockaddr_in('port','hostname'))ssl=OpenSSL::SSL::SSLSocket.new(s,sslcert)ssl.connect从这里开始,如果ssl连接和底层套接字仍然是ESTABLISHED,或者它是否在默认值7200之后进入CLOSE_WAIT,我想检查一个线程几秒钟甚至更糟的是在实际上不需要.write()或.read()的情况下关闭。是用select()、IO.select()还是其他方法完成

  10. ruby-on-rails - 需要帮助最大化多个相似对象中的 3 个因素并适当排序 - 2

    我需要用任何语言编写一个算法,根据3个因素对数组进行排序。我以度假村为例(如Hipmunk)。假设我想去度假。我想要最便宜的地方、最好的评论和最多的景点。但是,显然我找不到在所有3个中都排名第一的方法。Example(assumingthereare20importantattractions):ResortA:$150/night...98/100infavorablereviews...18of20attractionsResortB:$99/night...85/100infavorablereviews...12of20attractionsResortC:$120/night

随机推荐