[bzoj5101][POI2018]Powódź_并查集

Powódź bzoj-5101 POI-2018

题目大意:在地面上有一个水箱,它的俯视图被划分成了$n$行$m$列个方格,相邻两个方格之间有一堵厚度可以忽略不计的墙,水箱与外界之间有一堵高度无穷大的墙,因此水不可能漏到外面。已知水箱内每个格子的高度都是$[0,H]$之间的整数,请统计有多少可能的水位情况。因为答案可能很大,请对$10^9+7$取模输出。两个情况不同当且仅当存在至少一个方格的水位在两个情况中不同。

注释:$1le n imes mle 10^5$,$1le Hle 10^9$。


想法:神题一道。

不好想啊不好想

如果水位高于两个块之间的墙那么这两个块我们称之为连通,用并查集维护。

这样的话我们将墙墙们按照高度排序。

每次如果墙左右的两个联通块不连通的话就连一起。

假设$g_x$为祖先为$x$的联通块内部的答案。这个答案的是以联通块内的最后一次完成内部合并的墙墙高度最大值。

再维护$h_x$为该联通块内的那个高度。

$val[i]$为当前枚举的墙墙高度。(墙墙已经被排好序了。

所以更新后:$g_x=(g_x+val[i]-h_x) imes(g_y+val[i]-h_y)$。

最后我们再加上$H-h[find(1)]$即可。

Code:

#include <bits/stdc++.h>
#define M 1000010 
#define N 500010 
#define mod 1000000007 
using namespace std;
int f[N],g[N],h[N],cnt;
struct Node {int x,y,val;}a[M]; inline bool cmp_val(const Node &a,const Node &b) {return a.val<b.val;}
inline char nc() {static char *p1,*p2,buf[100000]; return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;}
int rd() {int x=0; char c=nc(); while(!isdigit(c)) c=nc(); while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=nc(); return x;}
int find(int x) {return f[x]==x?x:f[x]=find(f[x]);}
inline bool merge(int x,int y)
{
	x=find(x); y=find(y);
	if(x==y) return true;
	f[y]=x; return false;
}
inline void add(int x,int y,int z) {a[++cnt].x=x,a[cnt].y=y,a[cnt].val=z;}
int main()
{
	int n=rd(),m=rd(),H=rd(); for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m-1;j++) {int x=rd(); add((i-1)*m+j,(i-1)*m+j+1,x);}
	}
	for(int i=1;i<=n-1;i++)
	{
		for(int j=1;j<=m;j++) {int x=rd(); add((i-1)*m+j,i*m+j,x);}
	}
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
	{
		f[(i-1)*m+j]=(i-1)*m+j;
		g[(i-1)*m+j]=1;
	}
	sort(a+1,a+cnt+1,cmp_val);
	for(int i=1;i<=cnt;i++)
	{
		if(!merge(a[i].x,a[i].y))
		{
			int x=find(a[i].x),y=find(a[i].y);
			g[x]=1ll*(g[x]+a[i].val-h[x])*(g[y]+a[i].val-h[y])%mod;
			h[x]=a[i].val;
		}
	}
	printf("%d
",(g[find(1)]+H-h[find(1)])%mod);
	return 0;
}

小结:并查集的应用好题。

原文地址:https://www.cnblogs.com/ShuraK/p/10096058.html