woj 1032 Find the Max NORM 暴力

缘起

看群里有人水这道题目~ 遂做了 ~ woj 1032 Find the Max NORM

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
就是给你一个m*n的01矩阵(1<=m<=10, 1<=n<=3000), 现在你可以对这个矩阵有如下两种操作
1. 将一行取反(即0变1,1变0)
2. 将一列取反

问你随意怎么操作, 能达到的矩阵的最大norm是多少?
矩阵的norm的定义是所有矩阵元素之和.

【输入】
多样例. 对于每个样例, 先输入m和n ,然后是m*n的0或者1.

【输出】
输出最大norm

【样例输入】
2 2
0 1
1 0
2 2
0 1
1 1

【样例输出】
4
3

【限制】
时间限制: 1000 ms
内存限制: 524288 KiB

本题只需要注意到如下2个事实就行了.

  1. 所有操作都是满足交换律的, 所以可以先将1~m行的操作放在最前, 然后是1~n列的操作放在后面
  2. m较小, 可以枚举 0~(1<<m)-1, 即行的翻转方法. 然后对每种行的翻转方法, 列怎么翻转呢? 只需统计一列中1的个数多还是0的个数多, 如果1的个数多的话, 就不需要翻转此列, 如果0的个数较多的话, 则翻转此列即可.

此过程中维护答案即可.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//#include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <ctype.h>
using namespace std;
//#define LOCAL

int m,n, a[15][3005], col[3005], coll[3005]; // col[i]表示原始的第i列的1的个数,coll是col的副本

void read(int &x)
{
x = 0;
int f = 1;
char c = getchar();
while(!isdigit(c))
{
if (c=='-')
{
f = -1;
}
c = getchar();
}
while(isdigit(c))
{
x = (x<<3)+(x<<1)+(c^48);
c = getchar();
}
x*=f;
}

void write(int x)
{
if (x<0)
{
putchar('-');
x = -x;
}
if (x>9)
{
write(x/10);
}
putchar('0'+x%10);
}

int kk(int x)
{
memcpy(coll, col, sizeof(col));
for (int i =0; i<m; i++)
{
if (x&(1<<i)) // 变换第i行
{
for (int j = 0; j<n; j++)
{
a[i][j]?--coll[j]:++coll[j]; // 1变0,0变1
}
}
} // 经过x代表的行变化之后, 各列的1的个数变化了
int ans = 0;
for (int i = 0;i<n; i++) // 遍历每一列
{
ans+=max(coll[i], m-coll[i]); // 0和1中个数较多的那一个贡献给ans
}
return ans;
}

int main() {
#ifdef LOCAL
freopen("d:\\data.in", "r", stdin);
freopen("d:\\my.out", "w", stdout);
#endif
while(~scanf("%d%d", &m,&n))
{
memset(col, 0, sizeof(col));
int ans = 0;
for (int i = 0;i<m; i++)
{
for (int j =0;j<n; j++)
{
read(a[i][j]);
}
}
for (int i = 0;i<n; i++)
{
for (int j =0;j<m; j++)
{
if (a[j][i])
{
++col[i];
}
}
}
for (int i = 0;i<(1<<m); i++) // 暴力枚举行的操作
{
ans = max(ans, kk(i));
}
write(ans);
puts("");
}
return 0;
}

但是遗憾的是, 上面的代码被T掉了. 和ac程序对拍过, 应该不是wa.

那么上面的代码慢在哪里呢? 其实就慢在每次kk调用的时候, 都统计了一遍col. 而统计一次col的复杂度是O(m*n)的.而 m*n 不大不小也到达了3w啊~

所以优化的重点在于如何高效的维护col, 注意, 如果2个行变换方案A和B比较相近的话, 则B的kk中维护各列1的个数完全可以基于A的col结果. 而不需要再次花费O(nm)时间维护. 但是我们遍历所有(1<<m)种选择方案的顺序就要变化了. 可以使用dfs. 因为dfs就是一次选择某行, 一次不选择某行, 他们的col结果是相似的, 可以高效维护。

而不能使用上面暴力方法来打.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//#include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <ctype.h>
using namespace std;
//#define LOCAL

int m,n, a[15][3005], col[3005],ans; // col[i]表示原始的第i列的1的个数

void read(int &x)
{
x = 0;
int f = 1;
char c = getchar();
while(!isdigit(c))
{
if (c=='-')
{
f = -1;
}
c = getchar();
}
while(isdigit(c))
{
x = (x<<3)+(x<<1)+(c^48);
c = getchar();
}
x*=f;
}

void write(int x)
{
if (x<0)
{
putchar('-');
x = -x;
}
if (x>9)
{
write(x/10);
}
putchar('0'+x%10);
}

void kk(int x) // 变换第x行(0<=x<m), O(n)维护col和a
{
for (int i = 0;i<n; i++)
{
if (a[x][i])
{
--col[i];
}
else
{
++col[i];
}
a[x][i] = 1-a[x][i];
}
}

int mm()
{
int ret =0;
for (int i = 0;i<n;i++)
{
ret+=max(col[i], m-col[i]);
}
return ret;
}

void dfs(int cur) // 当前决定第cur行的选取(0<=cur<m)
{
if (cur==m)
{
ans = max(ans, mm());
return;
}
dfs(cur+1);
kk(cur); // 选择第cur行
dfs(cur+1);
kk(cur); // 反选第cur行
}

int main() {
#ifdef LOCAL
freopen("d:\\data.in", "r", stdin);
freopen("d:\\my.out", "w", stdout);
#endif
while(~scanf("%d%d", &m,&n))
{
memset(col, 0, sizeof(col));
ans = 0;
for (int i = 0;i<m; i++)
{
for (int j =0;j<n; j++)
{
read(a[i][j]);
}
}
for (int i = 0;i<n; i++)
{
for (int j =0;j<m; j++)
{
if (a[j][i])
{
++col[i];
}
}
} // 各列1的个数的初始情况
dfs(0);
write(ans);
puts("");
}
return 0;
}

ac情况

1
2
3
4
5
提交通过
#74836
code C++11
av_timer 267 ms
memory 464 KiB