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
*/

Codeforces 1109F - Sasha and Algorithm of Silence's Sounds(LCT)的更多相关文章

  1. CodeForces 1109F. Sasha and Algorithm of Silence's Sounds

    题目简述:给定一个$n \times m$的二维矩阵$a[i][j]$,其中$1 \leq nm \leq 2 \times 10^5$,矩阵元素$1 \leq a[i][j] \leq nm$且互不 ...

  2. Codeforces Round #539 (Div. 1) 1109F. Sasha and Algorithm of Silence's Sounds LCT+线段树 (two pointers)

    题解请看 Felix-Lee的CSDN博客 写的很好,不过最后不用判断最小值是不是1,因为[i,i]只有一个点,一定满足条件,最小值一定是1. CODE 写完就A,刺激. #include <b ...

  3. [CF1109F]Sasha and Algorithm of Silence's Sounds

    题意 有一个\(n*m\)的网格,每个格子有一个数,为\(1\)~\(n * m\)的排列 一个区间\((1<=l<=r<=n*m)\)是好的,当且仅当:数值在该区间内的格子,构成一 ...

  4. CF1109F Sasha and Algorithm of Silence's Sounds LCT、线段树

    传送门 构成一棵树可以分成两个限制:图不成环.图的点数-边数=1. 我们考虑枚举右端点\(r\)计算所有可能的左端点\(l\)的答案.我们先考虑第一个限制:图不成环.注意到当\(r\)确定的时候,满足 ...

  5. Codeforces 1137F - Matches Are Not a Child's Play(LCT)

    Codeforces 题面传送门 & 洛谷题面传送门 考虑将一个点 \(x\) 的编号变为当前所有点编号最大值 \(+1\) 会对每个点的删除时间产生怎么样的影响.由于编号最大的点肯定是最后一 ...

  6. Codeforces Round #394 (Div. 2) C.Dasha and Password(暴力)

    http://codeforces.com/contest/761/problem/C 题意:给出n个串,每个串的初始光标都位于0(列)处,怎样移动光标能够在凑出密码(每个串的光标位置表示一个密码的字 ...

  7. Codeforces Round #394 (Div. 2) E. Dasha and Puzzle(分形)

    E. Dasha and Puzzle time limit per test 2 seconds memory limit per test 256 megabytes input standard ...

  8. Codeforces Round #573 (Div. 2) E. Tokitsukaze and Duel (博弈)

    time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...

  9. Codeforces Round #621 (Div. 1 + Div. 2)D dij(思维)

    题:https://codeforces.com/contest/1307/problem/D 题意:给定无向图,n为点,m为边.在给个k,为特殊点的数目,题目要求在这些特殊点上连一条边,让新图最短路 ...

随机推荐

  1. NXOpen.UF.UFView.CycleObjects 的使用

    Public Sub CycleObjects(ByVal view As NXOpen.Tag, ByVal type As NXOpen.UF.UFView.CycleObjectsEnum, B ...

  2. 【数据结构与算法Python版学习笔记】树——二叉查找树 Binary Search Tree

    二叉搜索树,它是映射的另一种实现 映射抽象数据类型前面两种实现,它们分别是列表二分搜索和散列表. 操作 Map()新建一个空的映射. put(key, val)往映射中加入一个新的键-值对.如果键已经 ...

  3. Sharding-JDBC自定义复合分片算法

    Sharding-JDBC自定义复合分片算法 一.背景 二.需求 1.对于客户端操作而言 2.对于运营端操作而言 三.分片算法 1.客户id和订单id的生成规则 2. 确定数据落在那个表中 3.举例说 ...

  4. USB_ID OTG

    谁知道USB_ID pin 脚的功能意义?是干什么用的?USB 中不就有 VDD,GND,USB+,USB- 并没有USB_ID 的信息呀?检测ID脚状态高低,从而判断为主设备或从设备,otg的时候用 ...

  5. STM32中按键中断分析

    在按键学习中,我们有用到查询的方法来判断按键事件是否发生,这种查询按键事件适用于程序工作量较少的情况下,一旦程序中工作量较大较多,则势必影响程序运行的效率,为了简化程序中控制的功能模块的执行时间,引入 ...

  6. accept error: Too many open files

    今天测试socket服务器同一时间处理多个客户端连接问题,第一次测试1000个的时候没问题,第二次测试1000个服务器accept的时候就报错了 accept error: Too many open ...

  7. Redis安装、配置和卸载

    1.安装 mkdir /usr/local/redis 添加目录 wget [http://download.redis.io/releases/redis-4.0.1](http://downloa ...

  8. C++11 多线程同步 互斥锁 条件变量

    在多线程程序中,线程同步(多个线程访问一个资源保证顺序)是一个非常重要的问题,Linux下常见的线程同步的方法有下面几种: 互斥锁 条件变量 信号量 这篇博客只介绍互斥量和条件变量的使用. 互斥锁和条 ...

  9. NAT & 防火墙

    NAT 网络地址转换 NAT产生背景 今天,无数快乐的互联网用户在尽情享受Internet带来的乐趣.他们浏览新闻,搜索资料,下载软件,广交新朋,分享信息,甚至于足不出户获取一切日用所需.企业利用互联 ...

  10. VSCode 微信小程序 开发环境配置 详细教程

    本博客已暂停更新,需要请转新博客http://www.whbwiki.com/231.html 配置 VsCode 微信小程序开发环境并非不用官方的 微信小程序开发者工具 ,而是两者配合适用,可以极大 ...