今天做了下csust的一场比赛,这是A题,先是枚举每一行超时了一次,之后又wa了数次。。。最后还是艰难的ac了。。。同一道题,1A和10A会得到两种完全不同的喜悦感!
题目:小oy是一位文艺青年,他园艺精湛,在自家后院里种满了美丽的线段树。他家后院是一个由n*m个方格组成的矩阵,有些方格上种有线段树。小oy认为:若每个格子的相邻四格(上下左右)的线段树加起来都恰好是偶数棵,那么后院就是美观的。小oy拔掉或新建一棵线段树都需要消耗1点体力,现在他想知道让后院变得美观最少需要消耗多少体力值。
(四角落位置只有两格相邻,边界非角落位置只有三格相邻)
输入:第一行两个正整数n,m。(n<=10, m<=100)接下来n行,每行一个长m的字符串,代表该行的初始状态。(#表示线段树,-表示空地)
输出:最小消耗体力值
Sample Input :
2 4
#-#-
-#--
Sample output:
3
用状态压缩+枚举。状态压缩:用一个数组A表示原矩阵,有线段树的地方置为1,空地置为0。但需要注意的是本题n和m的范围!n<=10 , m<=100,如果枚举每一行的状态,那时间复杂度为O(2^m *n^2) ,肯定超时,所以应该枚举每一列!在输入的时候将行与列转换一下,时间复杂度为O(2^n*m^2),完全可以接受。。。具体方法是:枚举每个第一列,然后依题意确定每个下一列(对于b[r-1][c],已知sum = b[r-2][c]+b[r-1][c-1]+b[r-1][c+1],则当且仅当b[r][c]=sum%2时点(r-1, c)才能满足题意),不过要注意最后一列需要单独判断,因为这个wa了好多次。。。
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn = 111;
const int INF = 1e9;
int A[maxn][maxn], b[maxn][maxn], n, m;
int check(int s)
{
memset(b, 0, sizeof(b));
for(int c=0; c<n; c++) // 枚举排列
{
if(s & (1<<c)) b[0][c] = 1;
}
for(int r=1; r<m; r++)
for(int c=0; c<n; c++)
{
int sum = 0; //sum为b[r-1][c]的上 左 右 三个之和
if(r > 1) sum += b[r-2][c];
if(c > 0) sum+= b[r-1][c-1];
if(c < n-1) sum+= b[r-1][c+1];
b[r][c] = sum%2; //由sum确定b[r][c] 使b[r-1][c]满足题意
}
for(int i=0; i<n; i++) //单独判断最后一列是否满足
{
int sum = 0;
if(i > 0) sum+=b[m-1][i-1];
if(i < n-1) sum+=b[m-1][i+1];
if(m > 1) sum+=b[m-2][i];
if(sum%2) return INF;
}
int cnt = 0;
for(int r=0; r<m; r++)
for(int c=0; c<n; c++)
if(A[r][c] != b[r][c]) cnt++;//求体力值
return cnt;
}
int main()
{
char ch[maxn];
while(cin >> n >> m)
{
int sum = 0;
for(int i=0; i<n; i++)
{
cin>>ch;
for(int j=0; j<m; j++)
{
if(ch[j] == '#')
{
A[j][i] = 1;//行与列转换
sum++;
}
else
{
A[j][i] = 0;
}
}
}
int ans = INF;
for(int s=0; s<(1<<n); s++) // 枚举所有列
{
ans = min(ans, check(s));
}
if(ans == INF) ans = sum; //若无解 则删除所有‘#’
cout<<ans<<endl;
}
}