HNOI2016模拟4.10 线性代数与逻辑 简化条件后的简单DP

题目大意

01 矩阵 A B定义运算 C=AB ,满足 Cij=AijBij ,其中 ab=b¬a ,对应的c++代码为 b || !a 。现在给你一个 A 矩阵,要求你求出一个B矩阵,要求 AB 为全 1 的矩阵并且存在一个序列C,使得 Bij=Ci ^ Cj 。有 T 组测试数据,每组数据给出一个NN的矩阵 A ,要求输出B中最多有多少个 1

T100 N1000 N22000000

解题思路

分析一下 A B矩阵的性质,由于 AB 得到的是一个全 1 矩阵,根据的运算原则,如果 Aij 等于 1 ,那么可以得出Bij必定为 1 ,因为如果Bij不为1就不满足得到矩阵全为 1 的性质。那么顺带可得出Ci=Cj ^ 1 。而当Aij等于 0 时,那Bij取什么值都是合法的,也就是对 Ci Cj 没有限制。那么我们再来分析一下答案什么叫最大化矩阵 B 1的个数,其实就是在符合条件的情况下使 C 序列中0的个数乘 1 的个数最大。

分析清楚题目的本质后,那么思路就清晰了。由于矩阵是对称的,我们只需考虑对角线以上的情况,最后把答案乘2就可以了。

对于不能相等的Aij等于 1 的情况我们就可以从i j 连一条边保证Ci不能等于 Cj 。那么构出来就要求是一个二分图,如果不是肯定无解。那么 C 序列中的点就会分成很多联通快,而对于一个联通快,我们可以是二分图左边的点为1,右边为 0 或者反过来。这样对于每一个联通块就有两种方案。由于最后答案是所有联通块的0的个数乘 1 的个数,为了保证答案最优,我们再用一个Dp Fij 表示做到第 i 个联通块,0的个数为 j 1最多有多少个,由于每个联通块只有两种情况,所以简单转移一下就可以得出答案了。

代码

//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

const int MAXN = 1e3 + 5, MAXM = MAXN * MAXN * 4;

int N, Num, Fa[MAXN], Col[MAXN], Cnt[MAXN][2], F[MAXN][MAXN], A[MAXN][MAXN];
int tot, Next[MAXM], Last[MAXN], Go[MAXM];
bool Flag;

int Link(int u, int v) {
    Next[++ tot] = Last[u], Last[u] = tot, Go[tot] = v;
}

int Get(int Now) {
    return (Fa[Now] == Now) ? Now : Fa[Now] = Get(Fa[Now]);
}

void Dfs(int Now, int Pre) {
    Cnt[Num][Col[Now]] ++;
    for (int p = Last[Now]; p; p = Next[p]) {
        int v = Go[p];
        if (v == Pre) continue;
        if (Col[v] != -1 && Col[v] == Col[Now]) {Flag = 1; return;}
        if (Col[v] != -1) continue;
        Col[v] = Col[Now] ^ 1;
        Dfs(v, Now);
        if (Flag) return;
    }
}

void GetBlock() {
    memset(Last, 0, sizeof Last);
    for (int i = 1; i <= N; i ++) Fa[i] = i;
    for (int i = 1; i <= N - 1; i ++)
        for (int j = i + 1; j <= N; j ++) {
            if (!A[i][j]) continue;
            Link(i, j), Link(j, i);
            int F1 = Get(i), F2 = Get(j);
            if (F1 != F2) Fa[F1] = F2;
        }
}

void Work() {
    scanf("%d", &N);
    Flag = 0;
    for (int i = 1; i <= N; i ++) 
        for (int j = 1; j <= N; j ++) scanf("%d", &A[i][j]);
    for (int i = 1; i <= N; i ++) if (A[i][i] == 1) Flag = 1;
    tot = Num = 0;
    GetBlock();
    memset(Col, 255, sizeof Col), memset(Cnt, 0, sizeof Cnt), memset(F, 255, sizeof F);
    for (int i = 1; i <= N; i ++) 
        if (Get(i) == i) {
            Num ++, Col[i] = 0;
            Dfs(i, 0);
            if (Flag) break;
        }
    if (Flag) {printf("-1\n"); return;}

    F[0][0] = 0;
    int Ans = 0;
    for (int i = 1; i <= Num; i ++) {
        for (int j = 0; j <= N; j ++) {
            int Num0 = Cnt[i][0], Num1 = Cnt[i][1];
            if (j >= Num0 && F[i - 1][j - Num0] != -1) F[i][j] = max(F[i][j], F[i - 1][j - Num0] + Num1);
            if (j >= Num1 && F[i - 1][j - Num1] != -1) F[i][j] = max(F[i][j], F[i - 1][j - Num1] + Num0);
            if (i == Num) Ans = max(Ans, F[i][j] * j);
        }
    }   
    printf("%d\n", Ans * 2 - tot);
}

int main() {
    int Test;
    scanf("%d", &Test);
    for (; Test; Test --) Work();
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值