1. 程式人生 > 其它 >Codeforces 1109F - Sasha and Algorithm of Silence's Sounds(LCT)

Codeforces 1109F - Sasha and Algorithm of Silence's Sounds(LCT)

LCT+two pointers

Codeforces 題面傳送門 & 洛谷題面傳送門

講個笑話,這題是 2020.10.13 dxm 講題時的一道例題,而我剛好在一年後的今天,也就是 2021.10.13 學 LCT 時做到了這道題。。。。。。

首先考慮一個區間的匯出子圖是一棵樹的充要條件。顯然這些點組成的邊集不能有環,其次這些點組成的匯出子圖必須滿足 \(|V|-|E|=1\),而對於一個不存在環的圖必然有 \(|V|-|E|\ge 1\),因此這兩個條件加在一起就組成了一個區間符合條件的充要條件。

考慮如何維護之,注意到當我們固定了左端點 \(l\),那麼一個右端點符合“匯出子圖不存在環”的條件,當且僅當右端點小於一個固定值 \(R_l\)

,並且這個固定值隨著 \(l\) 的增大而增大,因此可以考慮 two pointers 維護這個右端點。可以發現判定一個右端點是否合法時需要支援以下操作:加入一個點,刪除一個點,判定兩點是否在同一連通塊中。經典的 LCT 模型,使用 LCT 維護連通性的套路維護。

接下來如何維護 \(|V|-|E|=1\) 這個條件,我們考慮建一棵線段樹,下標為 \(r\) 的位置維護當前 \([l,r]\) 組成的匯出子圖中,\(|V|-|E|\) 的值。當我們向右移動左端點 \(l\) 時,\(|V|-|E|\) 的變化都可以表示為一段字尾的值加上 \(+1\)\(-1\) 的形式,可以區間賦值解決。而每次查詢等價於查詢一個區間中 \(1\)

的個數,注意到由於這個區間中的匯出子圖不存在環,因此 \(|V|-|E|\ge 1\),即如果區間記憶體在 \(1\) 那麼 \(1\) 肯定是這段區間的最小值,使用維護最小值個數的線段樹即可解決。

時間複雜度 \(\mathcal O(nm\log nm)\)

然後就是實現的事情了,碼量可能略有點大,使用 namespace 會使程式碼更具有可讀性(

const int MAXN=2000;
const int MAXM=2e5;
const int dx[]={1,0,-1,0};
const int dy[]={0,1,0,-1};
int n,m,a[MAXN+5][MAXN+5];pii pos[MAXM+5];
int getid(int x,int y){return (x-1)*m+y;}
struct node{int ch[2],f,rev_lz;} s[MAXM+5];
int ident(int k){return (s[s[k].f].ch[0]==k)?0:((s[s[k].f].ch[1]==k)?1:-1);}
void connect(int k,int f,int op){s[k].f=f;if(~op) s[f].ch[op]=k;}
void rotate(int x){
	int y=s[x].f,z=s[y].f,dx=ident(x),dy=ident(y);
	connect(s[x].ch[dx^1],y,dx);connect(y,x,dx^1);connect(x,z,dy);
}
void tag(int k){swap(s[k].ch[0],s[k].ch[1]);s[k].rev_lz^=1;}
void pushdown(int k){
	if(s[k].rev_lz){
		if(s[k].ch[0]) tag(s[k].ch[0]);
		if(s[k].ch[1]) tag(s[k].ch[1]);
		s[k].rev_lz=0;
	}
}
void pushall(int k){if(~ident(k)) pushall(s[k].f);pushdown(k);}
void splay(int k){
	pushall(k);
	while(~ident(k)){
		if(ident(s[k].f)==-1) rotate(k);
		else if(ident(k)==ident(s[k].f)) rotate(s[k].f),rotate(k);
		else rotate(k),rotate(k);
	}
}
void access(int k){
	int pre=0;
	for(;k;pre=k,k=s[k].f) splay(k),s[k].ch[1]=pre;
}
void makeroot(int k){access(k);splay(k);tag(k);}
int findroot(int k){
	access(k);splay(k);
	while(s[k].ch[0]) pushdown(k),k=s[k].ch[0];
	splay(k);return k;
}
bool link(int x,int y){
	makeroot(x);
	if(findroot(y)==x) return 0;
//	printf("link %d %d\n",x,y);
	s[x].f=y;return 1;
}
void split(int x,int y){makeroot(x);access(y);splay(y);}
void cut(int x,int y){
	makeroot(x);
	if(findroot(y)!=x) return;
	if(s[y].f!=x||s[y].ch[0]) return;
//	printf("cut %d %d\n",x,y);
	s[y].f=s[x].ch[1]=0;
}
bool chkcon(int x,int y){makeroot(x);return (findroot(y)==x);}
bool vis[MAXM+5];
int cnt[MAXM+5];
void calc_ini(){//calculate how many edges are there in the induces subgraph of 1~i
	for(int i=1;i<=n*m;i++){
		cnt[i]=cnt[i-1];int x=pos[i].fi,y=pos[i].se;
		for(int d=0;d<4;d++){
			int nx=x+dx[d],ny=y+dy[d];
			if(nx<1||nx>n||ny<1||ny>m) continue;
			if(vis[a[nx][ny]]) cnt[i]++;
		} vis[i]=1;
	}
}
struct dat{
	int mn,cnt;
	dat(int _mn=0,int _cnt=0):mn(_mn),cnt(_cnt){}
	dat operator +(const dat &rhs){
		dat res;res.mn=min(mn,rhs.mn);
		if(res.mn==mn) res.cnt+=cnt;
		if(res.mn==rhs.mn) res.cnt+=rhs.cnt;
		return res;
	}
};
namespace segtree{
	struct node{int l,r,lz;dat v;} s[MAXM*4+5];
	void pushup(int k){s[k].v=s[k<<1].v+s[k<<1|1].v;}
	void build(int k,int l,int r){
		s[k].l=l;s[k].r=r;if(l==r) return s[k].v=dat(l-::cnt[l],1),void();
		int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);pushup(k);
	}
	void tag(int k,int v){s[k].v.mn+=v;s[k].lz+=v;}
	void pushdown(int k){if(s[k].lz) tag(k<<1,s[k].lz),tag(k<<1|1,s[k].lz),s[k].lz=0;}
	void modify(int k,int l,int r,int v){
		if(l<=s[k].l&&s[k].r<=r) return tag(k,v),void();
		pushdown(k);int mid=s[k].l+s[k].r>>1;
		if(r<=mid) modify(k<<1,l,r,v);
		else if(l>mid) modify(k<<1|1,l,r,v);
		else modify(k<<1,l,mid,v),modify(k<<1|1,mid+1,r,v);
		pushup(k);
	}
	dat query(int k,int l,int r){
		if(l<=s[k].l&&s[k].r<=r) return s[k].v;
		pushdown(k);int mid=s[k].l+s[k].r>>1;
		if(r<=mid) return query(k<<1,l,r);
		else if(l>mid) return query(k<<1|1,l,r);
		else return query(k<<1,l,mid)+query(k<<1|1,mid+1,r);
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){
		scanf("%d",&a[i][j]);
		pos[a[i][j]]=mp(i,j);
	} calc_ini();memset(vis,0,sizeof(vis));
	segtree::build(1,1,n*m);ll res=0;
	for(int l=1,r=1;l<=n*m;l++){
		while(r<=n*m){
			bool flg=1;int x=pos[r].fi,y=pos[r].se;
			for(int d=0;d<4;d++){
				int nx=x+dx[d],ny=y+dy[d];
				if(nx<1||nx>n||ny<1||ny>m) continue;
				if(vis[a[nx][ny]]) flg&=link(getid(x,y),getid(nx,ny));
			} if(!flg){
				for(int d=0;d<4;d++){
					int nx=x+dx[d],ny=y+dy[d];
					if(nx<1||nx>n||ny<1||ny>m) continue;
					cut(getid(x,y),getid(nx,ny));
				} break;
			} vis[r]=1;r++;
		} //printf("%d %d\n",l,r);
		dat d=segtree::query(1,l,r-1);
		if(d.mn==1) res+=d.cnt;
		vis[l]=0;int x=pos[l].fi,y=pos[l].se;
		for(int d=0;d<4;d++){
			int nx=x+dx[d],ny=y+dy[d];
			if(nx<1||nx>n||ny<1||ny>m) continue;
			if(a[nx][ny]>l) segtree::modify(1,a[nx][ny],n*m,1);
			cut(getid(x,y),getid(nx,ny));
		} segtree::modify(1,l,n*m,-1);
	} printf("%lld\n",res);
	return 0;
}
/*
2 3
1 2 3
4 5 6
*/