[CCO 2019]Sirtet——可并堆+懒标记加速模拟

[CCO 2019]Sirtet

前言

我不明白,这题完完全全是按模拟的思路来做的,为什么有人说它的本质是做差分约束的Dijkstra?不应该差分约束Dijkstra的本质是做模拟才对吗?

题解

首先考虑暴力的模拟怎么做,正常的思路应该是让所有没有接触地面的刚体同时向下移动,直到某一个刚体接触地面,然后把接触地面的刚体视作地面,再让其余刚体继续下落。暴力模拟的复杂度是 O ( n 2 m ) O(n^2m) O(n2m) 的。

想一想怎么加速这个过程。把连通的刚体用并查集缩为一个点,然后考虑每个#下面最近的#。我们把每个#对应的连通块向下面最近的#对应的连通块连边(有向边),特别地,如果下面是地面就向地面连边,边的长度为两个#之间的距离或#到地面的距离。边的数量显然最多是 n m nm nm 数量级的。

不妨先记录一下连向每个连通块的所有边以及连向地面的所有边,这可能是很有用的信息。我们分析一下暴力模拟的过程,每次下落,连通块与连通块之间的边的长度不会变,而连向地面的每条边长度减1。直到其中一条边长度为0的时候这条边的起点对应的连通块就会接触地面。第一条长度为0的边其实就是连向地面的长度最短的边,所以我们可以先把所有连向地面的边放进一个堆里面,然后取出最短的边,把对应连通块加进“地面”,就可以加速这个过程。

在这个过程中,其它连向地面的边的长度都会减去最短的边的长度,同时,连向这个被算作地面的连通块的边,会成为新的一批连向地面的边。我们想要同时快速维护这两者,只需要打一个带懒标记的可并堆即可。每个块都用可并堆维护一个边集,把原先的连向地面的边打上懒标记后,再把块上的可并堆合并进来即可。我们在模拟的同时记录一下每个连通块下落的距离,就可以把原图每个#挪到正确位置。

每次取出和合并必定会减少一条边,所以总复杂度最多 O ( n m log ⁡ n m ) O(nm\log nm) O(nmlognm)。这个复杂度跑不到上界,而且即使加了懒标记,可并堆常数仍然非常小。

代码

#include<cstdio>//JZM yyds!!
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<ctime>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<random>
#define ll long long
#define uns unsigned
#define MOD
#define MAXN 1000005
#define INF 1e18
#define IF it->first
#define IS it->second
using namespace std;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0)f^=(s=='-'),s=getchar();
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
inline void print(int x){
	if(x<0)putchar('-'),print(-x);
	else{
		if(x/10>0)print(x/10);
		putchar(x%10+'0');
	}
}
inline ll lowbit(ll x){return x&-x;}

struct itn{
	int sn[2],a,d,lz;itn(){}
	itn(int A,int D){
		a=A,d=D,sn[0]=sn[1]=0,lz=0;
	}
}t[MAXN];
int IN,rt[MAXN],root;
inline bool cmp(int a,int b){return a>b;}
inline void cover(int x,int d){
	if(!x||!d)return;
	t[x].lz+=d,t[x].a-=d;
}
inline void pushd(int x){
	if(!x||!t[x].lz)return;
	cover(t[x].sn[0],t[x].lz);
	cover(t[x].sn[1],t[x].lz);
	t[x].lz=0;
}
mt19937 Rand;
inline int mergh(int x,int y){
	if(!x||!y)return x^y;
	pushd(x),pushd(y);
	if(cmp(t[x].a,t[y].a))swap(x,y);
	bool o=Rand()%2;
	if(!t[x].sn[o^1])o^=1;
	t[x].sn[o]=mergh(t[x].sn[o],y);
	return x;
}

char s[MAXN],as[MAXN];
int n,m,fa[MAXN],ds[MAXN],drp[MAXN];
bool dpd[MAXN];
inline int finds(int x){
	return fa[x]==x?x:(fa[x]=finds(fa[x]));
}
inline void unions(int x,int y){
	int u=finds(x),v=finds(y);
	if(u^v)fa[v]=u;
}
signed main()
{
	Rand.seed(*new(int));
	n=read(),m=read();
	for(int i=0;i<n*m;i++)fa[i]=i;
	for(int i=0;i<n;i++)scanf("%s",s+i*m);
	for(int i=1;i<n;i++)
		for(int j=0;j<m;j++)
			if(s[i*m+j]=='#'){
				if(s[(i-1)*m+j]=='#')
					unions((i-1)*m+j,i*m+j);
			}
	for(int i=0;i<n;i++)
		for(int j=1;j<m;j++)
			if(s[i*m+j]=='#'){
				if(s[i*m+j-1]=='#')
					unions(i*m+j-1,i*m+j);
			}
	for(int i=n-2;~i;i--)
		for(int j=0;j<m;j++){
			if(s[(i+1)*m+j]=='#')ds[i*m+j]=0;
			else ds[i*m+j]=ds[(i+1)*m+j]+1;
		}
	for(int i=n-1;~i;i--)
		for(int j=0;j<m;j++){
			if(s[i*m+j]=='.')continue;
			int u=i*m+j,vi=i+ds[u]+1;
			if(vi>=n){
				t[++IN]=itn(ds[u],finds(u));
				root=mergh(root,IN);
			}else{
				int v=finds(vi*m+j);
				t[++IN]=itn(ds[u],finds(u));
				rt[v]=mergh(rt[v],IN);
			}
		}
	ll ad=0;
	while(root){
		int u=root;pushd(root);
		root=mergh(t[root].sn[0],t[root].sn[1]);
		if(dpd[t[u].d])continue;
		ad+=t[u].a,dpd[t[u].d]=1,drp[t[u].d]=ad;
		cover(root,t[u].a);
		root=mergh(root,rt[t[u].d]);
	}
	for(int i=n*m-1;~i;i--)as[i]='.';
	for(int i=0;i<n;i++)
		for(int j=0;j<m;j++)
			if(s[i*m+j]=='#'){
				int u=finds(i*m+j);
				as[(i+drp[u])*m+j]='#';
			}
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++)putchar(as[i*m+j]);
		putchar('\n');
	}
	return 0;
}
上一篇:PowerDesigner的Table视图显示Columns的Code信息


下一篇:wxy 4.12 牛客练习赛61 重现