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();
}

你可能感兴趣的:(dp)