「LuoguP4147」 玉蟾宫(并查集
题目背景
有一天,小猫rainbow和freda来到了湘西张家界的天门山玉蟾宫,玉蟾宫宫主蓝兔盛情地款待了它们,并赐予它们一片土地。
题目描述
这片土地被分成N*M个格子,每个格子里写着'R'或者'F',R代表这块土地被赐予了rainbow,F代表这块土地被赐予了freda。
现在freda要在这里卖萌。。。它要找一块矩形土地,要求这片土地都标着'F'并且面积最大。
但是rainbow和freda的OI水平都弱爆了,找不出这块土地,而蓝兔也想看freda卖萌(她显然是不会编程的……),所以它们决定,如果你找到的土地面积为S,它们每人给你S两银子。
输入输出格式
输入格式:第一行两个整数N,M,表示矩形土地有N行M列。
接下来N行,每行M个用空格隔开的字符'F'或'R',描述了矩形土地。
输出格式:输出一个整数,表示你能得到多少银子,即(3*最大'F'矩形土地面积)的值。
输入输出样例
说明
对于50%的数据,1<=N,M<=200
对于100%的数据,1<=N,M<=1000
题解
这是来自我校学长(祖传)的并查集做法
首先从上到下枚举每一行(分割线)。
在当前行,把它以上的F染色,那么F会构成这样的图案↓
每一列在这条分割线以上有多少连续的F,可以在每次下移分割线时顺便用O(m)扫一遍维护。(如果这一格为F,那么连续F为之前这列的连续F加1;否则为0)
然后我们按照每一列的F高度排序,每次把F最高的取出来。
如果它左边一列被取过了,我们就把当前列和它左边一列并起来;如果右边一列被取过,就把这一列和右边一列并起来。
最后询问一下这一列的祖先有多少个子节点,也就是这一列以它的高度往左右最多能扩展的宽度。
原理:在当前列之前被并入这个祖先的列的长度一定大于等于当前列的长度,并且这些列互相相邻。
然后当前列的高度,乘上它往左右最多能扩展的宽度,就是取这一列,且高度等于这一列的最大矩形面积了。(就算还有相同高度的没有处理,之后做那一列的时候也会得到最优解)
实现的时候只需要把两个需要合并的列的祖先节点$fa[v]=u,siz[u]+=siz[v]$就可以了,因为只有祖先节点的siz是有意义的。
因为懒所以直接用了优先队列,并查集只路径压缩也是$O(mlogm)$的时间,总复杂度$O(nmlogm)$
1 /* 2 qwerta 3 P4147 玉蟾宫 4 Accepted 5 100 6 代码 C++,1.14KB 7 提交时间 2018-10-14 22:07:46 8 耗时/内存 9 1724ms, 916KB 10 */ 11 #include<iostream> 12 #include<cstdio> 13 #include<queue> 14 using namespace std; 15 int s[1003];//记录每列往上F的高度 16 int fa[1003],siz[1003];//并查集 17 bool sf[1003];//标记每一列是否被用过 18 struct emm{ 19 int nod,v; 20 }; 21 struct cmp{ 22 bool operator()(emm qaq,emm qwq){ 23 return qaq.v<qwq.v; 24 } 25 };//重载()运算符(用来给优先队列排序 26 priority_queue<emm,vector<emm>,cmp>q; 27 int fifa(int x) 28 { 29 if(fa[x]==x)return x; 30 return fa[x]=fifa(fa[x]); 31 } 32 void con(int x,int y)//把x列和y列并起来 33 { 34 int u=fifa(x),v=fifa(y); 35 fa[v]=u; 36 siz[u]+=siz[v]; 37 return; 38 } 39 int main() 40 { 41 //freopen("a.in","r",stdin); 42 ios::sync_with_stdio(false); 43 cin.tie(false),cout.tie(false);//关闭同步流(让cin变快 44 int n,m; 45 cin>>n>>m; 46 int ans=0; 47 for(int c=1;c<=n;++c)//从上往下移分割线 48 { 49 for(int i=1;i<=m;++i) 50 { 51 char ch; 52 cin>>ch; 53 if(ch=='F'){s[i]++;q.push((emm){i,s[i]});} 54 else s[i]=0; 55 } 56 for(int i=1;i<=m;++i) 57 fa[i]=i,siz[i]=1,sf[i]=0;//初始化 58 while(!q.empty()) 59 { 60 int i=q.top().nod,x=q.top().v;q.pop(); 61 sf[i]=1;//标记这一列取过了 62 if(sf[i-1])con(i-1,i);//如果左边取过了就并起来 63 if(sf[i+1])con(i,i+1);//如果右边取过了就并起来 64 int fi=fifa(i);//找祖先节点 65 ans=max(ans,siz[fi]*x); 66 } 67 } 68 cout<<ans*3;//输出最大矩形面积*3 69 return 0; 70 }