Codeforces Round #663 (Div. 2) D-505

题目要求每一个平方子矩阵里1都为奇数
那么我们先分析,最小的子矩阵肯定是 2 × 2 2\times 2 2×2,然后就是 4 × 4 4\times 4 4×4 的矩阵了,然而假如 2 × 2 2\times 2 2×2 的矩阵里,1都是奇数个,那 4 × 4 4\times 4 4×4 矩阵里,怎么都是偶数,所以min(n,m)肯定不能大于等于4,否则直接输出-1,那么我们现在可以确定n和m至少有一个是小于等于3的了。

再继续分析,我们设n是小的,m是大的(总之这个矩阵你可以随便转90°,要是n比m大你就转一下呗),假如n是1,就一行,啥子矩阵都没,那直接输出0喽。

假如n是2呢?比如现在,

1 0 1 
0 1 0

你会发现因为n是2,所以子矩阵的高就是2,那么枚举子矩阵的左右边界就行了,第一列的1的数量是1个,第二列的数量是1个,第一列和第二列可以拼成一个 2 × 2 2\times 2 2×2 矩阵,那就是把第一列或者第二列修改1次,然后第二列又和第三列可以拼成一个子矩阵,那么就是第二列或者第三列修改一次,现在长度是3,那把长度拓展后呢?
这很明显是个dp问题。
n是3的时候呢?

1 0 1
0 1 1
0 1 0

比如这个,一共可以拼出4个子矩阵,左上,左下,右上,右下,这样的话就是相当于在n=2的基础上再额外加个判断,n=2只需要判断某一列的第一行和第二行,n=3的时候需要判断某一列的第一行和第二行以及第二行和第三行,这里我们可以通过状压来省去很多步骤,3行,每行有0或者1两种状态,所以0~7可以依次对应一下8种情况。

0 1 2 3 4 5 6 7

0 1 0 1 0 1 0 1
0 0 1 1 0 0 1 1
0 0 0 0 1 1 1 1

这样就把3行直接压成了一行,并且比较第一行和第二行的时候,直接比较a[i]%4即可,比较第二行和第三行的时候,直接比较a[i]>>1即可,如何统计两列中1个总个数呢?

1 0 1
0 1 1
0 1 0

就比如第一列和第二列现在拼子矩形,可以拼出

1 0  和 0 1
0 1     0 1

那么用二进制表示就是1,2和0,3, 1 ∧ 2 = 3 1 \land 2 =3 12=3, 0 ∧ 3 = 3 0 \land 3=3 03=3,他们俩个合并后都是3,而3的1共有2个,所以第一列和第二列不能直接组合,这个时候枚举第一列和第二列各自什么状态才能组合,最多需要枚举 8 × 8 8 \times 8 8×8
一共需要枚举m位,所以复杂度是 m × 8 × 8 m \times 8 \times 8 m×8×8,绰绰有余。
具体可以见代码。

#include <bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=a;i<n;i++)
#define per(i,a,n) for(int i=n-1;i>=a;i--)
typedef long long ll;
typedef double db;
typedef pair<int,int> PII;
typedef vector<int> VI;
const ll mod=1000000007;
const int maxn=5000005;
const int inf=0x3f3f3f3f;
const int INF=0x7f7f7f7f;
ll gcd(ll a,ll b){ return b?gcd(b,a%b):a;}
#define ms(a) memset(a,0,sizeof(a))
#define mss(a) memset(a,-1,sizeof(a))
#define msi(a) memset(a,inf,sizeof(a))
#define iossync ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
// head
string s1;
int k[10];
int a,b[maxn];
int dp[maxn][10];
int n,m;
bool check(int x,int y)
{
	return k[(x^y)]&1;
}
void fact1()
{
	rep(i,0,8)
	dp[0][i]=k[b[0]^i];//转换成i形式需要花费多少
	rep(i,1,m)
	{
		rep(j,0,8)
		{
			rep(z,0,8)
			{
				if(check(j>>1,z>>1) && check(j%4,z%4))
				{//上下同时满足奇数 
					dp[i][z]=min(dp[i][z],dp[i-1][j]+k[(b[i])^z]);
				}
			}
		}
	}
	rep(i,0,8)
	{
		dp[m-1][0]=min(dp[m-1][i],dp[m-1][0]);
	}
	cout<<dp[m-1][0]<<"\n";
}
void fact()
{
	rep(i,0,4)
	dp[0][i]=k[b[0]^i];
	rep(i,1,m)
	{
		rep(j,0,4)
		{
			rep(z,0,4)
			{
				if(check(j,z))
				{//一共就2行,比较一次就好 
					dp[i][z]=min(dp[i][z],dp[i-1][j]+k[(b[i])^z]);
				}
			}
		}
	}
	rep(i,0,4)
	{
		dp[m-1][0]=min(dp[m-1][i],dp[m-1][0]);
	}
	cout<<dp[m-1][0]<<"\n";
}
int main()
{
	msi(dp);
	rep(i,0,8)
	k[i]=k[i>>1]+(i&1);//预处理0~8里各包含几个1 
	cin>>n>>m;
	if(min(n,m)>=4)
	{
		while(n--)
		cin>>s1;
		cout<<"-1\n";
		return 0;
	}
	//默认n<m
	if(m<n)
	{
		rep(i,0,m)
		{
			rep(j,0,n)
			{
				scanf("%1d",&a);
				b[j]|=(a<<i);
			}
		}
		swap(n,m);
	}
	else
	{
		rep(i,0,n)
		{
			rep(j,0,m)
			{
//				scanf("%1d",&a[i][j]);
				scanf("%1d",&a);
				b[j]|=(a<<i);
			}
		}
	}
	switch(n)
	{
		case 1:{
			cout<<"0\n";
			break;
		}
		case 2:{
			fact();
			break;
		}
		case 3:{
			fact1();
			break;
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值