题意
一个(N*M)的矩阵,其中“.”代表空地,“0-9”代表古代建筑,我们如果选择了一个编号的古代建筑想要建立,那么对应就要将全部该编号的建筑建立起来,如果在空地上建筑,只建立当前点。问最多能够建立多少种建筑,并且每两种建筑之间没有公共边。
分析
注意了,古代建筑的公共边是他的事,你不能选两个相邻的古代建筑。那么,我们把古代建筑抽象成一个点, 把剩下的各个现代的农场块抽象成各个点,给曼哈顿距离为1的块连一条边,那么这条题目就成了在这个新图上找图的最大独立集(注意了,这里和其他的题解不太一样,这里没有保证建的模型是二分图,所以仅仅是求图的最大独立集)。
那么如何求图的最大独立集?在这里总结一下三种最大独立集的写法。
- 树的最大独立集。这是树形dp的经典问题,定义(d[i])为以(i)为节点的子树的最大独立集大小,那么对于i节点,我们可以去考虑两种决策选或不选。如果选,它的儿子节点没法选了(也就是考虑它的孙子节点),但是答案+1;否则就可以考虑它的儿子节点,但是这个节点对答案不产生贡献。这样一来,就有了dp公式:$$d[i]=max(C_{max}[i],G_{max}[i]+1)$$,其中(C_{max}[i])为i的孩子的dp和,(G_{max}[i])为i的孙子的dp和。如果无根树先转化成有根树。典型问题见我POJ-2342的题解。
- 二分图的最大独立集。这同样是二分图的经典问题,(n)减去最大匹配数就可以了,简单说下原因(具体的见我HDU-1068的题解):对于非最大匹配上的点他们一定是彼此独立的(如果有两个空点相连就存在增广路了),那么再加上最大匹配的彼此一个点,就是它的最大独立集。
- 图的最大独立集。这是这题的写法。图的最大独立集转化为补图的最大团来解决。最大团的定义见我POJ-2989的题解。典型问题见我POJ-1419的题解。
然后简单的说下我这里最大团的求法。这位dalao的博客给我很多启发。下标从0开始:
定义(dp[i])是(i)点从自己到(n-1)这些点的最大团的节点个数(用mx更新)。然后在dfs中枚举每一个节点,看这些节点与哪些编号比它大的点相连,然后记录他们(按照递归深度在stk数组)并递归处理之。
接下来考虑剪枝(不然复杂度是(O(n2^n)),最大团是个NP完全问题),简单地,如果目前的最大团个数加上自己要去的节点之后的哪怕所有点都无法比当前最优解更好,剪枝;类似地,如果当前个数加上之后要去的节点到最后的最大团个数还是不如最优解,剪枝。
然后这题就写出来了。
另一种写法
对于这样的一个矩阵,如果没有古代建筑,二分图是有经典建模的:根据((i+j)\%2)来把矩阵的点分割成两个点集,边仍然是按照上面那个曼哈顿距离的思路做建模。然后很好做了,跑一遍最大匹配,然后n*m-最大匹配/2(记得除以2,无向图)就可以了。
那么这里既然有不超过10个古代建筑,暴力枚举之,然后把所有古代建筑的点和与选中古代建筑的曼哈顿距离为1的格子去掉然后做最大匹配,按照没有古代建筑的思路做就可以了。网上有代码。
代码
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <map>
#define MP make_pair
#define PB emplace_back
#define fi first
#define se second
#define ZERO(x) memset((x), 0, sizeof(x))
#define ALL(x) (x).begin(),(x).end()
#define rep(i, a, b) for (repType i = (a); i <= (b); ++i)
#define per(i, a, b) for (repType i = (a); i >= (b); --i)
#define QUICKIO
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
using namespace std;
using ll=long long;
using repType=int;
const int MAXN=105;
int mat[12][12];
vector<int> vec;
map<int, int> idx;
int n,m;
bool graph[MAXN][MAXN];
int dp[MAXN];
int mx;
int stk[MAXN][MAXN];
void
init()
{
ZERO(mat);
memset(graph,true,sizeof(graph));
vec.clear();
idx.clear();
}
const int dx[]={1,0,-1,0},
dy[]={0,1,0,-1};
int query(int x)
{
if(idx.find(x)==idx.end())
{
vec.PB(x);
return idx[x]=int(vec.size())-1;
}
else return idx[x];
}
void dfs(int N,int num,int step)
{
if(num==0)
{
if(step>mx)
{
mx=step;
}
return;
}
for(int i=0;i<num;i++)
{
int k = stk[step][i];
if(step+N-k<=mx) return ;
if(step+dp[k]<=mx) return ;
int cnt = 0;
for(int j=i+1;j<num;j++)
if(graph[k][stk[step][j]])
{
stk[step+1][cnt++]=stk[step][j];
}
dfs(N,cnt,step+1);
}
}
void run(int N)
{
mx=0;
for(int i=N-1;i>=0;i--)
{
int sz=0;
for(int j=i+1;j<N;j++)
if(graph[i][j]) stk[1][sz++]=j;
dfs(N,sz,1);
dp[i]=mx;
}
}
int
main()
{
QUICKIO
int T; cin>>T;
rep(kase,1,T)
{
init();
cin>>n>>m;
int blank_area=0;
rep(i,1,n) rep(j,1,m)
{
char chr;
cin>>chr;
if(chr=='.') mat[i][j]=10+(blank_area++);
else
{
mat[i][j]=chr-'0';
/*
if(ma.find(mat[i][j])==ma.end())
{
vec.PB(mat[i][j]);
ma[mat[i][j]]=int(vec.size())-1;
}
*/
}
}
rep(i,1,n) rep(j,1,m)
{
int gx=query(mat[i][j]);
rep(k,0,3)
{
int ti=i+dx[k],
tj=j+dy[k];
if(ti>=1 && ti<=n && tj>=1 && tj<=m)
{
int gy=query(mat[ti][tj]);
if(gx!=gy)
graph[gx][gy]=graph[gy][gx]=false;
}
}
}
int gsz=vec.size();
run(gsz);
cout<<"Case #"<<kase<<": "<<dp[0]<<endl;
}
return 0;
}