您的位置:首页 > 其它

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

2016-04-12 17:43 363 查看

题目大意

对0101矩阵AA,BB定义运算C=A⊗BC = A \otimes B,满足Cij=Aij⊗BijC_{ij} = A_{ij} \otimes B_{ij},其中a⊗b=b∨¬aa \otimes b = b\vee \neg a,对应的c++代码为b || !ab ~ ||~ !a。现在给你一个AA矩阵,要求你求出一个BB矩阵,要求A⊗BA \otimes B为全11的矩阵并且存在一个序列CC,使得 ∀Bij=Ci\forall B_{ij} = C_i ^CjC_j。有TT组测试数据,每组数据给出一个N∗NN*N的矩阵AA,要求输出BB中最多有多少个11。

T≤100T \leq 100,N≤1000N \leq 1000,∑N2≤2000000\sum N^2 \leq 2000000

解题思路

分析一下AA,BB矩阵的性质,由于A⊗BA \otimes B得到的是一个全11矩阵,根据⊗\otimes的运算原则,如果AijA_{ij}等于11,那么可以得出BijB_{ij}必定为11,因为如果BijB_{ij}不为1就不满足得到矩阵全为11的性质。那么顺带可得出Ci=CjCi = Cj ^ 11。而当AijA_{ij}等于00时,那BijB_{ij}取什么值都是合法的,也就是对CiC_i和CjC_j没有限制。那么我们再来分析一下答案什么叫最大化矩阵BB中11的个数,其实就是在符合条件的情况下使CC序列中00的个数乘11的个数最大。

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

对于不能相等的AijA_{ij}等于11的情况我们就可以从ii向jj连一条边保证CiC_i不能等于CjC_j。那么构出来就要求是一个二分图,如果不是肯定无解。那么CC序列中的点就会分成很多联通快,而对于一个联通快,我们可以是二分图左边的点为11,右边为00或者反过来。这样对于每一个联通块就有两种方案。由于最后答案是所有联通块的00的个数乘11的个数,为了保证答案最优,我们再用一个DpDp,FijF_{ij}表示做到第ii个联通块,00的个数为jj时11最多有多少个,由于每个联通块只有两种情况,所以简单转移一下就可以得出答案了。

代码

//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();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: