zoukankan      html  css  js  c++  java
  • Codeforces 1109F

    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(nmlog 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
    ",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
    ",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
    ",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
    ",res);
    	return 0;
    }
    /*
    2 3
    1 2 3
    4 5 6
    */
    
  • 相关阅读:
    创业指南:如何实现打工族的老板梦
    在C#中压缩解压缩文件(适合.Net1.x)
    35岁之前成功的12条黄金法则
    郑州DOTNET俱乐部《DotNet实战之旅》活动邀请
    1baiwan.com你能走多远?(原创,请任意转载,作者:小张.net)
    MongoDB实战开发 【零基础学习,附完整Asp.net示例】
    TFS2010强制撤签锁定项
    持续集成理论和实践的新进展
    JQuery最佳实践
    IE下实现全屏两方法
  • 原文地址:https://www.cnblogs.com/ET2006/p/Codeforces-1109F.html
Copyright © 2011-2022 走看看