「CF 1391D」 505 滚木题解
「CF 1391D」 505 滚木题解
题目大意
(自行理解)
还是讲一下吧。给定一个矩阵,每个元素都是 \(0\) 或 \(1\) 。现在问你最少需要多少次修改才能使所有边长为偶数的子正方形矩阵内的 \(1\) 的数量为奇数。
唐人思路
首先你会自然而然的想到暴力,思考时间。每个小方格状态为 \(0\) 或 \(1\) ,也就是每个方格的状态有两个,那么总时间则为 \(2^{n \times m}\) (冷知识: \(1 \le n \le m \le 10^{6}\) 并且 \(1 \le n \times m \le 10^{6}\) ),显然过不了。
既然这个状态就两个,那试着状压一下。思考状压DP,转移肯定是行得通的,但是这时间也太大了吧。试着算算,状压DP是需要枚举所有状态的,所以就是 \(2^{n}\) 了,其次还要从一到 \(m\) 枚举,一共时间 \(O(m \times 2^{n})\) 这就比暴力优化一点😂。
怎么压时间???不妨大胆想象一下是否可以把 \(n\) 缩小一下(事实证明是可以的)。怎么缩小???
看看 \(m = n = 4\) 的情况:
0000
0000
0000
0000
怎么变???(这里你可以停下自己模拟一下)
可以发现变不出来,为什么???
左上角的 \(2 \times 2\) 子正方形矩阵:
00
00
这里不管他变成了什么状态,反正就是需要知道他的 \(1\) 的数量是奇数个。
同理,像这样的\(2 \times 2\) 子正方形矩阵还有右下角,左下角右下角的(暂且不要管中间的那几个 \(2 \times 2\) 子正方形矩阵 ,只需要先看看这几个边角上的)。
可以发现这几个边角上的 \(2 \times 2\) 子正方形矩阵互不重叠,所以整个 \(4 \times 4\) 的正方形矩阵总共的1的个数则是这四个子正方形矩阵的 \(1\) 的数量的总和。冷知识:偶数个奇数相加的和是偶数。所以整个 \(4 \times 4\) 的正方形矩阵总共的1的数量是偶数。题目要求他的所有边长为偶数的子正方形矩阵的 \(1\) 的数量是奇数(矩阵本身也算是他自己的子矩阵),所以 \(m = n = 4\) 的矩阵就一定不满足条件。换句话说,这个矩阵想要满足一个条件他就一定不会满足另一个条件,所以直接输出 \(-1\) 。
所以 \(m \ge n \ge 4\) 的矩阵的答案就是 \(-1\) 。因为\(m \ge n \ge 4\) 的矩阵中一定有 \(m = n = 4\) 的子矩阵,而 \(m = n = 4\) 的矩阵又被我们证过了,是不行的。
剩下的就是 \(n=1\) ,直接输出 \(0\) 很简单;
\(n=2\) ,就是奇偶交替也很简单;
\(n=3\) ,状压DP就行。
雷霆滚木代码
#pragma GCC optimize("Ofast,unroll-loops") // PYM祖传卡最优解的代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=105;
ll dp[1000005][10];
char a[50][1000005];
bool chk(ll x,ll y) // 判断当前状态是否合法
{
ll cnt1=0,cnt2=0;
for (ll i=0;i<2;i++)
{
if ((x>>i)&1) cnt1++;
if ((y>>i)&1) cnt1++;
}
if (!(cnt1&1)) return 0;
for (ll i=1;i<3;i++)
{
if ((x>>i)&1) cnt2++;
if ((y>>i)&1) cnt2++;
}
if (!(cnt2&1)) return 0;
return 1;
}
ll cost(ll x,ll p) // 计算需要修改的格子的数量
{
ll cnt=0;
for (ll i=0;i<3;i++)
{
if (((x>>i)&1)!=a[i+1][p]-'0') cnt++;
}
// cout <<cnt<<' ';
return cnt;
}
signed main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
ll n,m; cin >>n>>m;
if (n>3){cout <<-1;return 0;} // 大于3一定不合法
if (n==1){cout <<0;return 0;} // 等于1代价为0
for (ll i=1;i<=n;i++)
{
for (ll j=1;j<=m;j++)
{
cin >>a[i][j];
}
}
if (n==2) // n为2的情况
{
ll minn=0x3f3f3f3f,ans=0;
if ((a[1][1]-'0'+a[2][1]-'0')%2!=1) ans++;
for (ll i=2;i<=m;i++)
{
if ((a[1][i]-'0'+a[2][i]-'0')%2!=(i%2)) ans++;
}
minn=min(minn,ans);ans=0;
if ((a[1][1]-'0'+a[2][1]-'0')%2==1) ans++;
for (ll i=2;i<=m;i++)
{
if ((a[1][i]-'0'+a[2][i]-'0')%2==(i%2)) ans++;
}
minn=min(minn,ans);
cout <<minn;
return 0;
}
// 等于3时就直接状压
memset(dp,0x3f,sizeof dp);
for (ll i=0;i<(1<<3);i++) dp[1][i]=cost(i,1);
for (ll i=2;i<=m;i++)
{
for (ll k1=0;k1<(1<<3);k1++)
{
for (ll k2=0;k2<(1<<3);k2++)
{
if (!chk(k1,k2)) continue;
dp[i][k2]=min(dp[i][k2],dp[i-1][k1]+cost(k2,i));
}
}
}
ll minn=0x3f3f3f3f3f3f3f3f;
for (ll i=0;i<(1<<3);i++) minn=min(minn,dp[m][i]); // 枚举各个状态找最小
cout <<minn;
return 0;
}
问题
万一 \(n \ge m\) 怎么办?
请读题。
状压DP具体怎么实现
其实就是表示状态,然后状态一直向后转移就行了。
为什么 \(n = 1\) 时不用改
因为没有偶数边长的子正方形矩阵。

浙公网安备 33010602011771号