Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others) |
【HDU5556 2015合肥赛区E】【最大团or二分图匹配】Land of Farms 不同编号不相邻条件下的最大农场数 最大团做法
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<ctype.h>
#include<math.h>
#include<map>
#include<set>
#include<vector>
#include<queue>
#include<string>
#include<algorithm>
#include<time.h>
#include<bitset>
using namespace std;
void fre(){freopen("c://test//input.in","r",stdin);freopen("c://test//output.out","w",stdout);}
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T> inline void gmax(T &a,T b){if(b>a)a=b;}
template <class T> inline void gmin(T &a,T b){if(b<a)a=b;}
const int N=12,M=0,Z=1e9+7,ms63=1061109567;
const int dy[4]={-1,0,0,1};
const int dx[4]={0,-1,1,0};
int casenum,casei;
int n,m,id;
char a[N][N]; //原始地图
int o[N][N]; //节点标号
bool e[105][105]; //表示最大团关系,e[i][j]==1表示i与j之间没有排斥关系
int f[105][105]; //把存边方式从邻接矩阵边转化为邻接链表,以此来加快速度
int mx[105]; //mx[i]表示最大团中编号最小的点的编号>=i时的最大团答案
int ans; //全局变量,最后得到的ans就是全局最大团
void color(int y,int x,char ch)
{
if(y<0||y==n||x<0||x==m||a[y][x]!=ch)return;
if(~o[y][x])return;o[y][x]=id;
for(int i=0;i<4;++i)color(y+dy[i],x+dx[i],ch);
}
void disable(int y1,int x1,int y2,int x2)
{
if(y2<0||y2==n||x2<0||x2==m)return;
int o1=o[y1][x1];
int o2=o[y2][x2];
if(o1==o2)return;
e[o1][o2]=e[o2][o1]=0;
}
void COLOR()
{
id=0;MS(o,-1);
for(int i=0;i<n;++i)
{
for(int j=0;j<m;++j)
{
if(a[i][j]=='.')
{
o[i][j]=id++;
}
else if(o[i][j]==-1)
{
color(i,j,a[i][j]);
id++;
}
}
}
}
void build_graph()
{
MS(e,1);
for(int i=0;i<n;++i)
{
for(int j=0;j<m;++j)
{
for(int k=0;k<4;k++)
{
disable(i,j,i+dy[k],j+dx[k]);
}
}
}
}
int mcp(int num,int p)
{
if(!p)//没有边则直接更新答案
{
if(num>ans){ans=num;return 1;}
else return 0;
}
for(int i=0;i<p;++i)//枚举当前节点可以延展的所有节点
{
//如果把接下来的所有点都加入最大团中,还无法更新答案的话,返回no
if(num+p-i<=ans)return 0;
//如果把目前编号最小的节点所能延展的所有点都加进最大团中,还无法更新答案的话,返回no
int u=f[num][i];if(mx[u]+num<=ans)return 0;
int k=0;
for(int j=i+1;j<p;++j)if(e[u][f[num][j]])f[num+1][k++]=f[num][j];
if(mcp(num+1,k))return 1;
}
return 0;
}
void MCP()
{
MS(f,0);
MS(mx,0);
ans=0;
//从后向前,枚举每个点i为最大团中编号最小的点,以此为起点展开搜索
for(int i=id-1;~i;--i)
{
int k=0;
for(int j=i+1;j<id;++j)if(e[i][j])f[1][k++]=j;
mcp(1,k);
mx[i]=ans;
}
}
int main()
{
scanf("%d",&casenum);
for(casei=1;casei<=casenum;++casei)
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;++i)scanf("%s",a[i]);
COLOR();
build_graph();
MCP();
printf("Case #%d: %d\n",casei,ans);
}
}
/*
【trick&&吐槽】
题意说不清,题意有毒……这时需要枚举题意,猜解样例Orz
【题意】
给你一个n(10)*m(10)的棋盘。
格子上的'.'代表可以建造新式农场的点,每个新建的农场是独立的,拥有独立的编号。
格子上的'1'~'9'代表可以建造上古农场的点,相同的数字构成的联通块代表相同的上古农场,相同的上古农场共享一个编号。
如果我们要建造一个上古农场,就必须建立其整个联通块。
对于一个编号的农场,其上下左右相邻处不能有其他编号的农场。
基于以上原则,我们希望建造尽可能多数量的农场。
让你输出这个最大数量。
【类型】
最大团or二分图匹配
【分析】
首先看到这道题点数很少,最大供选择农场数才不过100,而且涉及到"我选了你就不能选"这样的限制条件,于是我们考虑到最大团。
于是——
第一步:求出所有联通块,也就是可能的不同编号的农场数量,并进行编号、染色。
第二步:然后初始化一个全为1的bool矩阵,e[i][j]==1的话表示i和j编号的农场可以同时选。
之后我们对于每个点,向上下左右四个方向走,如果编号不同,那么不同这2个农场不能同时选,在e[][]中标记。
之后对e[][]跑个最大团即可。
本代码里的最大团模板是0base,就是节点编号从0开始,速度很快哦~~啦啦啦!
然而,这道题的正解是二分图匹配,其时间复杂度为O(10^2 * nm * VE)。
O(VE)是匈牙利算法的时间复杂度,最坏情况下是100*200,也就是可以控制使得单组时间复杂度为2e7。
【数据】
1
10 10
..........
..........
..........
..........
..........
..........
..........
..........
..........
..........
*/
【HDU5556 2015合肥赛区E】【最大团or二分图匹配】Land of Farms 不同编号不相邻条件下的最大农场数 匈牙利做法
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<ctype.h>
#include<math.h>
#include<map>
#include<set>
#include<vector>
#include<queue>
#include<string>
#include<algorithm>
#include<time.h>
#include<bitset>
using namespace std;
void fre(){freopen("c://test//input.in","r",stdin);freopen("c://test//output.out","w",stdout);}
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T> inline void gmax(T &a,T b){if(b>a)a=b;}
template <class T> inline void gmin(T &a,T b){if(b<a)a=b;}
const int N=12,M=0,Z=1e9+7,ms63=1061109567;
const int dy[4]={-1,0,0,1};
const int dx[4]={0,-1,1,0};
int casenum,casei;
int n,m,num,ans;
char a[N][N];
bool e[N][N]; //e[i][j]==1表示(i,j)这个'.'是可以通过二分匹配的方式选取的
bool u[10]; //u[i]==1表示'0'+i这个数字是被我们枚举选取的
vector<int>b[105]; //b[i]存放i号点(i号点为偶点)可以匹配的点的编号
int p[105]; //p[j]表示j号点(j号点为奇点)实现匹配的点的编号
bool vis[105]; //vis[i]表示i号点(i号点是偶点)是否匹配访问过
map<int,int>mop;
bool color()
{
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)if(isdigit(a[i][j])&&u[a[i][j]-'0'])
{
for(int d=0;d<4;d++)
{
int y=i+dy[d];
int x=j+dx[d];
if(y<0||y==n||x<0||x==m)continue;
if(a[y][x]=='.')e[y][x]=0;
else if(a[y][x]!=a[i][j]&&u[a[y][x]-'0'])return 0;
}
}
}
return 1;
}
void build_graph()
{
for(int i=0;i<n*m;i++)b[i].clear();
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)if(a[i][j]=='.'&&e[i][j]&&(i+j)%2==0)
{
for(int d=0;d<4;d++)
{
int y=i+dy[d];
int x=j+dx[d];
if(y<0||y==n||x<0||x==m||a[y][x]!='.'||!e[y][x]||(y+x)%2==0)continue;
int o1=i*m+j;
int o2=y*m+x;
b[o1].push_back(o2);
}
}
}
}
int hungary(int x)
{
vis[x]=1;
for(int i=b[x].size()-1;~i;i--)
{
int y=b[x][i];
if(p[y]==-1){p[y]=x;return 1;}
}
for(int i=b[x].size()-1;~i;i--)
{
int y=b[x][i];
if(!vis[p[y]]&&hungary(p[y])){p[y]=x;return 1;}
}
return 0;
}
void HUNGARY()
{
MS(p,-1);
int sum=0;
int tmp=0;
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)if(a[i][j]=='.'&&e[i][j])
{
++sum;
MS(vis,0);
if(hungary(i*m+j))++tmp;
}
}
gmax(ans,sum-tmp+num);
}
int main()
{
scanf("%d",&casenum);
for(casei=1;casei<=casenum;++casei)
{
scanf("%d%d",&n,&m);
ans=0;
for(int i=0;i<n;++i)scanf("%s",a[i]);
mop.clear();int id=0;
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)if(isdigit(a[i][j]))
{
if(mop.find(a[i][j])==mop.end())mop[a[i][j]]=id++;
a[i][j]=mop[a[i][j]]+'0';
}
}
int top=1<<id;
for(int i=0;i<top;i++)
{
MS(e,1);
MS(u,0);
int p=0;
int x=i;
num=0;
while(x)
{
if(x&1)u[p]=1,++num;
x>>=1;
++p;
}
if(color())
{
build_graph();
HUNGARY();
}
}
printf("Case #%d: %d\n",casei,ans);
}
}
/*
【trick&&吐槽】
题意说不清,题意有毒……这时需要枚举题意,猜解样例Orz
【题意】
给你一个n(10)*m(10)的棋盘。
格子上的'.'代表可以建造新式农场的点,每个新建的农场是独立的,拥有独立的编号。
格子上的'1'~'9'代表可以建造上古农场的点,相同的数字构成的联通块代表相同的上古农场,相同的上古农场共享一个编号。
如果我们要建造一个上古农场,就必须建立其整个联通块。
对于一个编号的农场,其上下左右相邻处不能有其他编号的农场。
基于以上原则,我们希望建造尽可能多数量的农场。
让你输出这个最大数量。
【类型】
最大团or二分图匹配
【分析】
2^10枚举选取的远古农场,
然后把选取的远古农场周围的'.'标记为不可选取。
对于剩余的可以选取的'.',从奇点向相邻的偶点连边,
然后跑二分图最大匹配,
用"可以选取的'.'总数-最大匹配数+枚举选取的远古农场数"更新答案
【数据】
input
1
10 10
..........
..........
..........
..........
..........
..........
..........
..........
..........
..........
output
50
*/