[CF1109F]Sasha and Algorithm of Silence's Sounds

Description

给出一个n * m的网格图,保证所有位置上的数形成一个1 ~ n* m的排列。
问有多少个值域区间[l,r]满足,[l,r]的数在网格图上的位置形成一棵树
这里的树指四连通求导出子图的意义下无环且只有一个连通块。
n*m<=200000,n,m<=1000

Solution

维护一条扫描线,对于扫到的右端点r,我们需要求出最左的左端点L,满足[L,r]无环
这个东西可以维护个two-pointer用LCT支持加点删点维护连通性
然后我们考虑连通块数=点-边,于是我们需要支持区间+1区间-1区间求=1的个数
注意到连通块数时刻>=1,于是用线段树维护区间最小值以及最小值的个数即可。
复杂度O(nm log nm)

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

typedef long long ll;

int read() {
	char ch;
	for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
	int x=ch-'0';
	for(ch=getchar();ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x;
}

const int N=2e5+5,M=1e3+5;

int n,m,a[M][M],b[N],id[N][2];
int g[4][2]={0,1,0,-1,1,0,-1,0};

int t[N][2],f[N],p[N],sta[N],top;
bool rev[N];

int son(int x) {return t[f[x]][1]==x;}

void rotate(int x) {
	int y=f[x],z=son(x);
	if (t[x][1-z]) f[t[x][1-z]]=y;
	if (f[y]) t[f[y]][son(y)]=x;
	else p[x]=p[y],p[y]=0;
	t[y][z]=t[x][1-z];t[x][1-z]=y;
	f[x]=f[y];f[y]=x;
}

void down(int x) {
	if (rev[x]) {
		swap(t[x][0],t[x][1]);
		rev[t[x][0]]^=1;rev[t[x][1]]^=1;
		rev[x]=0;
	}
}

void remove(int x,int y) {
	do {
		sta[++top]=x;
		x=f[x];
	} while (x!=y);
	for(;top;down(sta[top--]));
}

void splay(int x,int y) {
	remove(x,y);
	while (f[x]!=y) {
		if (f[f[x]]!=y)
			if (son(x)==son(f[x])) rotate(f[x]);
			else rotate(x);
		rotate(x);
	}
}

void access(int x) {
	int y=0;
	for(;x;y=x,x=p[x]) {
		splay(x,0);
		f[t[x][1]]=0;p[t[x][1]]=x;
		t[x][1]=y;f[y]=x;p[y]=0;
	}
}

void make_root(int x) {access(x);splay(x,0);rev[x]^=1;}

void link(int x,int y) {make_root(x);make_root(y);p[y]=x;}

void cut(int x,int y) {
	make_root(x);
	access(y);splay(y,0);
	f[x]=0;p[x]=0;t[y][0]=0;
}
	
int find_root(int x) {
	access(x);splay(x,0);
	while (t[x][0]) x=t[x][0];
	return x;
}

bool near(int x,int y) {
	if (find_root(x)!=find_root(y)) return 0;
	make_root(x);access(y);splay(x,0);splay(y,x);
	return t[y][0]==0;
}

void del(int i,int r) {
	int x=id[i][0],y=id[i][1];
	fo(k,0,3) {
		int xx=x+g[k][0],yy=y+g[k][1];
		if (xx<1||xx>n||yy<1||yy>m) continue;
		int v=a[xx][yy];
		if (v>=i&&v<=r&&near(v,i)) cut(v,i);
	}
}

struct Seg{int mn,cnt;}tr[N<<2];

int tag[N<<2];

void add(int v,int z) {tag[v]+=z;tr[v].mn+=z;}

void Down(int v) {
	if (tag[v]) {
		add(v<<1,tag[v]);
		add(v<<1|1,tag[v]);
		tag[v]=0;
	}
}

void update(int v) {
	int ls=v<<1,rs=v<<1|1;
	if (tr[ls].mn<tr[rs].mn) tr[v].cnt=tr[ls].cnt;
	if (tr[ls].mn>tr[rs].mn) tr[v].cnt=tr[rs].cnt;
	if (tr[ls].mn==tr[rs].mn) tr[v].cnt=tr[ls].cnt+tr[rs].cnt;
	tr[v].mn=min(tr[ls].mn,tr[rs].mn);
}

void build(int v,int l,int r) {
	if (l==r) {tr[v].cnt=1;return;}
	int mid=l+r>>1;
	build(v<<1,l,mid);build(v<<1|1,mid+1,r);
	update(v);
}

void modify(int v,int l,int r,int x,int y,int z) {
	if (x<=l&&r<=y) {add(v,z);return;}
	int mid=l+r>>1;Down(v);
	if (x<=mid) modify(v<<1,l,mid,x,y,z);
	if (y>mid) modify(v<<1|1,mid+1,r,x,y,z);
	update(v);
}

int query(int v,int l,int r,int x,int y) {
	if (x<=l&&r<=y) return tr[v].mn==1?tr[v].cnt:0;
	int mid=l+r>>1,sum=0;Down(v);
	if (x<=mid) sum+=query(v<<1,l,mid,x,y);
	if (y>mid) sum+=query(v<<1|1,mid+1,r,x,y);
	return sum;
}

int main() {
	n=read();m=read();
	fo(i,1,n) 
		fo(j,1,m) {
			a[i][j]=read();
			id[a[i][j]][0]=i;
			id[a[i][j]][1]=j;
		}
	int j=1;ll ans=0;
	build(1,1,n*m);
	fo(i,1,n*m) {
		int x=id[i][0],y=id[i][1];
		fo(k,0,3) {
			int xx=x+g[k][0],yy=y+g[k][1];
			if (xx<1||xx>n||yy<1||yy>m) continue;
			int v=a[xx][yy];
			if (v>=j&&v<=i) {
				while (v>=j&&v<=i&&find_root(i)==find_root(v)) del(j++,i);
				if (v>=j&&v<=i) link(v,i);
			}
		}
		modify(1,1,n*m,j,i,1);
		fo(k,0,3) {
			int xx=x+g[k][0],yy=y+g[k][1];
			if (xx<1||xx>n||yy<1||yy>m) continue;
			int v=a[xx][yy];
			if (v>=j&&v<=i) modify(1,1,n*m,j,v,-1);
		}
		ans+=query(1,1,n*m,j,i);
	}
	printf("%lld\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值