CF1109F 题解
简要题意:给定一个
n\times m 网格,格子中填有1\sim n\times m 的互不相同的权值。称一个区间[l,r] 合法当且仅当权值在[l,r] 内的格子形成一棵树。求有几个合法区间。n\times m\le 2\times 10^5 。
Do NOT use LCT!
首先数合法区间个数要想到考虑有用点对/禁止点对。
在这里“禁止点对”就是那些极小的会形成环的区间,把它们预处理出来(后面再看怎么处理),设
那么扫左端点,问题就变成了类似于“对于每个
保证无环有什么好处呢?这样经典“点
想到把贡献拆成点
剩下的问题是怎么找出
首先发现格子越多越可能形成环,所以
双指针的话就有两种想法:一种是直接双指针,这样需要支持删除,并查集做不了,于是就出来了 LCT 的做法;另一种是过左端点重构(又称尺取/带删双指针),这样需要支持左右两部分的合并,并查集还是做不了。
那并查集能做啥?能做撤销。
把眼光转向决策单调性,从“撤销”就会自然地想到整体二分。定义函数 solve(l,r,L,R) 求出 solve(l,mid-1,L,res) 和 solve(mid+1,r,res,R) 所新需要的点,递归求解。
两部分时间复杂度都为
#include "bits/stdc++.h"
#ifdef DEBUG
#include "PrettyDebug.hpp"
#else
#define debug(...) [](auto...){}(__VA_ARGS__)
#define debuga(...) [](auto...){}(__VA_ARGS__)
#endif
#define For(i,a,b) for(int i=a;i<=b;i++)
#define Rev(i,a,b) for(int i=a;i>=b;i--)
#define Fin(file) freopen(file,"r",stdin)
#define Fout(file) freopen(file,"w",stdout)
using namespace std; typedef long long ll;
constexpr int N=2e5+5;
class STree{
int n,mn[N<<2],cnt[N<<2],tag[N<<2];
#define k1 (k<<1)
#define k2 (k<<1|1)
void setg(int k,int x) { mn[k]+=x; tag[k]+=x; }
void pushdown(int k) { if(tag[k]) setg(k1,tag[k]),setg(k2,tag[k]),tag[k]=0; }
void update(int k) { mn[k]=min(mn[k1],mn[k2]); cnt[k]=(mn[k]==mn[k1]?cnt[k1]:0)+(mn[k]==mn[k2]?cnt[k2]:0); }
void build(int k,int l,int r){
if(l==r) return mn[k]=0,cnt[k]=1,void();
int mid=(l+r)>>1; build(k1,l,mid); build(k2,mid+1,r); update(k);
}
void modify(int k,int l,int r,int ql,int qr,int x){
if(ql<=l&&r<=qr) return setg(k,x),void();
int mid=(l+r)>>1; pushdown(k);
if(ql<=mid) modify(k1,l,mid,ql,qr,x);
if(mid+1<=qr) modify(k2,mid+1,r,ql,qr,x);
update(k);
}
int query(int k,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr) return mn[k]==1?cnt[k]:0;
int mid=(l+r)>>1,res=0; pushdown(k);
if(ql<=mid) res+=query(k1,l,mid,ql,qr);
if(mid+1<=qr) res+=query(k2,mid+1,r,ql,qr);
return res;
}
public:
void init(int _n) { n=_n; build(1,1,n); }
void modify(int l,int r,int x) { modify(1,1,n,l,r,x); }
int query(int l,int r) { return query(1,1,n,l,r); }
}T;
class DSU{
int fa[N],siz[N]; stack<int> S;
int getfa(int x) { return x==fa[x]?x:getfa(fa[x]); }
public:
void init(int n) { For(i,1,n) fa[i]=i,siz[i]=1; }
bool merge(int x,int y){
x=getfa(x); y=getfa(y); if(x==y) { S.push(0); return false; }
S.push(y); fa[y]=x; siz[x]+=siz[y]; return true;
}
void undo(){
int y=S.top(); S.pop(); if(y==0) return;
int x=fa[y]; siz[x]-=siz[y]; fa[y]=y;
}
}O;
int n,m,a[N],f[N],pi[N],pj[N],vis[N]; vector<int> lis[N];
inline int ID(int i,int j) { return (i-1)*m+j; }
bool Add(int o){
bool ok=true; vis[o]=1;
auto work=[&](int u) { if(vis[u]) ok&=O.merge(o,u); };
if(pi[o]>1) work(a[ID(pi[o]-1,pj[o])]);
if(pi[o]<n) work(a[ID(pi[o]+1,pj[o])]);
if(pj[o]>1) work(a[ID(pi[o],pj[o]-1)]);
if(pj[o]<m) work(a[ID(pi[o],pj[o]+1)]);
return ok;
}
void Del(int o){
vis[o]=0;
auto work=[&](int u) { if(vis[u]) O.undo(); };
if(pi[o]>1) work(a[ID(pi[o]-1,pj[o])]);
if(pi[o]<n) work(a[ID(pi[o]+1,pj[o])]);
if(pj[o]>1) work(a[ID(pi[o],pj[o]-1)]);
if(pj[o]<m) work(a[ID(pi[o],pj[o]+1)]);
}
void solve(int l,int r,int L,int R){ // guaranteed [r+1,L] filled
if(l>r) return;
debug(l,r,L,R);
if(L==R) { For(i,l,r) f[i]=L;; return; }
int mid=(l+r)>>1;
// For(i,1,n*m) assert(vis[i]==(i>r&&i<=L));
if(r<L){
Rev(i,r,mid) Add(i);
int res=L; while(res<R&&Add(res+1)) res++;
if(res!=R) Del(res+1);
f[mid]=res; debug(mid,res);
Rev(i,res,L+1) Del(i);
solve(l,mid-1,L,res);
For(i,mid,r) Del(i);
For(i,L+1,res) Add(i);
solve(mid+1,r,res,R);
Rev(i,res,L+1) Del(i);
}
else{
int res=mid-1; while(res<R&&Add(res+1)) res++;
if(res!=R) Del(res+1);
f[mid]=res; debug(mid,res);
Rev(i,res,mid) Del(i);
For(i,mid,L) Add(i);
solve(l,mid-1,L,res);
Rev(i,L,mid) Del(i);
For(i,r+1,res) Add(i);
solve(mid+1,r,res,R);
Rev(i,res,r+1) Del(i);
}
}
signed main(){
atexit([](){cerr<<"Time = "<<clock()<<" ms"<<endl;});
ios::sync_with_stdio(0); cin.tie(0);
cin>>n>>m; For(i,1,n) For(j,1,m) cin>>a[ID(i,j)],pi[a[ID(i,j)]]=i,pj[a[ID(i,j)]]=j;
For(i,1,n) For(j,1,m-1) { int x=a[ID(i,j)],y=a[ID(i,j+1)]; lis[min(x,y)].push_back(max(x,y)); }
For(i,1,n-1) For(j,1,m) { int x=a[ID(i,j)],y=a[ID(i+1,j)]; lis[min(x,y)].push_back(max(x,y)); }
O.init(n*m); solve(1,n*m,1,n*m); debuga(f,1,n*m);
T.init(n*m); ll ans=0;
Rev(i,n*m,1){
T.modify(i,n*m,1); for(int x:lis[i]) T.modify(x,n*m,-1);
ans+=T.query(i,f[i]);
}
cout<<ans<<'\n';
return 0;
}
// CONTINUE, NON-STOPPING, FOR CHARLIEY
// START TYPING IF YOU DON'T KNOW WHAT TO DO
// STOP TYPING IF YOU DON'T KNOW WHAT YOU'RE DOING
// Started Coding On: March 13 Wed, 15 : 07 : 36