题意

一个\(N*M\)的矩阵,其中“.”代表空地,“0-9”代表古代建筑,我们如果选择了一个编号的古代建筑想要建立,那么对应就要将全部该编号的建筑建立起来,如果在空地上建筑,只建立当前点。问最多能够建立多少种建筑,并且每两种建筑之间没有公共边

分析

注意了,古代建筑的公共边是他的事,你不能选两个相邻的古代建筑。那么,我们把古代建筑抽象成一个点, 把剩下的各个现代的农场块抽象成各个点,给曼哈顿距离为1的块连一条边,那么这条题目就成了在这个新图上找图的最大独立集(注意了,这里和其他的题解不太一样,这里没有保证建的模型是二分图,所以仅仅是求图的最大独立集)。

那么如何求图的最大独立集?在这里总结一下三种最大独立集的写法。

  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的题解。
  2. 二分图的最大独立集。这同样是二分图的经典问题,\(n\)减去最大匹配数就可以了,简单说下原因(具体的见我HDU-1068的题解):对于非最大匹配上的点他们一定是彼此独立的(如果有两个空点相连就存在增广路了),那么再加上最大匹配的彼此一个点,就是它的最大独立集。
  3. 图的最大独立集。这是这题的写法。图的最大独立集转化为补图的最大团来解决。最大团的定义见我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;
}

「国庆训练&知识学习」图的最大独立集与拓展(Land of Farms,HDU-5556)的更多相关文章

  1. 「日常训练&知识学习」单调栈

    这几天的知识学习比较多,因为时间不够了.加油吧,为了梦想. 这里写几条简单的单调栈作为题解记录,因为单调栈的用法很简单,可是想到并转化成用这个需要一些题目的积淀. 相关博客参见:https://blo ...

  2. 「日常训练&知识学习」树的分块(王室联邦,HYSBZ-1086)

    题意与分析 这题的题意就是树分块,更具体的看题目(中文题). 学习这一题是为了树的分块,为树上莫队做铺垫. 参考1:https://blog.csdn.net/LJH_KOQI/article/det ...

  3. 「日常训练&知识学习」树的直径(POJ-1849,Two)

    题意 一个城市由节点和连接节点的街道组成,街道是双向的. 此刻大雪覆盖了这个城市,市长确定了一些街道要将它们清扫干净,这些街道保证所有的节点可以通过它们连通而且街道数目尽可能小. 现有两台相同的扫雪机 ...

  4. 「日常训练&知识学习」莫队算法(二):树上莫队(Count on a tree II,SPOJ COT2)

    题意与分析 题意是这样的,给定一颗节点有权值的树,然后给若干个询问,每次询问让你找出一条链上有多少个不同权值. 写这题之前要参看我的三个blog:Codeforces Round #326 Div. ...

  5. 「国庆训练」Bomb(HDU-5934)

    题意 给定\(n\)个炸弹,每个炸弹的坐标与代价与影响范围给定,炸弹会引爆影响范围内其他所有炸弹.求引爆所有炸弹的最小代价. 分析 先做\(n^2\)的循环,然后建图,对\(i\)能引爆\(j\)建边 ...

  6. 「国庆训练」ArcSoft's Office Rearrangement(HDU-5933)

    题目与分析 题解见https://blog.csdn.net/cmershen/article/details/53200922. 训练赛场上我们写出来了--在4小时50分钟的时候...激情补题啊.. ...

  7. 「国庆训练」Kingdom of Obsession(HDU-5943)

    题意 给定\(s,n\),把\(s+1,s+2,...,s+n\)这\(n\)个数填到\(1,2,...,n\)里,要求\(x\)只能填到\(x\)的因子的位置(即题目中\(x\%y=0\)那么x才能 ...

  8. 「知识学习」二分图的最大匹配、完美匹配和匈牙利算法(HDU-2063)

    定义 如果一个图\((E,V)\)的顶点集\(E\)能够被能够被分成两个不相交的集合\(X,Y\),且每一条边都恰连接\(X,Y\)中的各一个顶点,那么这个图就是一个二分图. 容易得知,它就是不含有奇 ...

  9. 精心整理「服务器Linux C/C++」 成长路程(附思维导图)

    前言 我不是名校毕业,更没有大厂的背景,我只是一个毕业不到 2 年的普普通通的程序员,在摸爬滚打的工作这段时间里,深知了有一个「完整的知识体系」是非常重要的.当事人非常后悔没有在大学期间知道这个道理- ...

随机推荐

  1. js 实现商品放大镜效果

    知识点,需熟悉下面属性及含义: offsetLeft           //获取元素相对左侧的距离 (计算是从最左侧边框外开始) offsetTop           //获取元素相对顶部的距离 ...

  2. 调用save()方法,页面显示保存成功,但是数据库中没有值的原因

    在DAO层调用save()方法,页面上显示成功,但是在数据库中查找时发现数据没有保存到数据库中的原因可能是: 1.Service层中是否在调用DAO层中的save()方法之前添加注解@Transact ...

  3. java之sleep(),join(),yield(),wait(),notify()、notifyAll()区别

    1.sleep() 使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁.也就是说如果有synchronized同步快,其他线程仍然不能访问共享数据.注意该方 ...

  4. SQL 二

    1.执行DML语句的步骤:在数据库缓冲区缓存中搜索相关的块:将块从数据文件复制到缓冲区:将变更向量写入日志缓冲区:将变更向量应用于数据库缓冲区缓存.2.重做可以保护的段类型:索引段.表段.撤销段.3. ...

  5. 20181029NOIP模拟赛T2

    2.追捕 [题目背景] Duan2baka:“jmsyzsfq天下第一蠢!” jmsyzsfq:“你说什么?!” [题目描述] 于是Duan2baka开始了逃亡的旅程,而jmsyzsfq也开始追捕Du ...

  6. 学习笔记 - 中国剩余定理&扩展中国剩余定理

    中国剩余定理&扩展中国剩余定理 NOIP考完回机房填坑 ◌ 中国剩余定理 处理一类相较扩展中国剩余定理更特殊的问题: 在这里要求 对于任意i,j(i≠j),gcd(mi,mj)=1 (就是互素 ...

  7. MAMP:MySQL wasn't able to start

    MAMP 我点击start server的时候 发现mysql服务器打不开 http://images.cnblogs.com/cnblogs_com/lwwen/1231721/o_11111111 ...

  8. MySQL正则表达式的问题

    原本以为 正则表达式里面的特殊\d匹配数字放到sql语句里面也是适用的,没想到一直不匹配.但是放到编程语言java或者js里面又匹配.看了一下原来sql对正则的支持没有那么全面.一定要用[0-9]代表 ...

  9. Linux查看MAC地址方法

    注:一般默认的网卡文件名是eth0,根据IP地址对应的实际情况区判断是ethx即可. 1. ip -a . cat /sys/class/net/ens39/address  其中 HWaddr字段就 ...

  10. 面试题——Java虚拟机

    一.运行时数据区域 Java虚拟机在执行Java程序的时候会把它所管理的内存划分为若干个不同的数据区域,这些区域各有用途: 程序计数器:(线程私有的) 程序计数器是一块较小的内存,可以看作是当前线程所 ...