LOJ571 Misaka Network 与 Accelerator 题解
2-SAT+线段树优化建图+边分治
Statement
给定一棵 \(n\) 个点的树,\(m\) 条限制和区间 \([L,R]\)。你需要选出 \(n\) 个点的一个子集(可以为空或者全集),满足给出的所有限制。
每条限制形如:若 \(u\) 点(被选了/没被选),则树上与 \(u\) 距离在 \([L,R]\) 间的点都(被选了/没被选)
问是否有解。
\(1≤n,m,L,R≤5×10^4\)
Solution
搞了一晚上+半个上午。。。
看到这种限制方式,我们容易想到 2-SAT
看到一个点向多个点连边,我们可以想到线段树优化建图,直接套用 CF786B Legacy 的做法,开两个线段树 \(TreeIn,TreeOut\) 连边。
考虑的是到点 \(u\) 的距离在 \([L,R]\) 之内的点,关于树上路径长度,我们可以想到点分治(联想地,如果题面把“树上与 \(u\) 距离”改成子树内的点,我们可以使用 bfs 序)
但由于淀粉质涉及到多个子树之间,不好合并,我们不妨使用边分治
对于每一个分治边,我们对于边两端的子树分别建立起“动态开点权值线段树”,权值为点的深度
在线段树的叶子节点,我们再挂上对应深度的点具体是什么
注意由于我们要跑 2-SAT ,所以每一个点还要拆成两个,这里的处理方式是再来一棵线段树
对于一个左部点,我们根据限制条件的类型连边右部的线段树即可
这样我们有 \(O(\log )\) 个分治边,建立起来了 \(O(\log)\) 棵线段树,复杂度 \(O(n\log^2n)\)
然后就这样硬写,写完之后蒟蒻傻不拉几地盯着看了半天调不动,下面的代码仅作示意&纪念 (快略过)
//算空间真烦!
//双向边两倍空间,三度化两倍,线段树空间四倍,两棵8倍
#include<bits/stdc++.h>
#define ls lc[rt]
#define rs rc[rt]
#define mid ((l+r)>>1)
// #define id(rt,op,k) ((k-1)*(N<<2)+rt+K*op)
// 点 rt,在 op(0/1)In/Out 中,第 k 棵树
#define ori(i,op) (M+n*op+i)
/*
0 [1,n] 为在 TreeIn 中点的选择点
1 [n+1,2n] 为在 TreeOut 中点的选择点
2 [2n+1,3n] 为在 TreeIn 中点的不选择点
3 [3n+1,4n] 为在 TreeOut 中点的不选择点
*/
#define Go(E,u) for(int e=E.head[u],v;v=E.edge[e].to,e;e=E.edge[e].nex)
using namespace std;
const int N = 5e4+5;
const int M = 2e6;
const int K = 1e6;
char buf[1<<23],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read(){;
int s=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))s=s*10+(ch^48),ch=getchar();
return s*w;
}
bool cmin(int &a,int b){return a>b?a=b,1:0;}
bool cmax(int &a,int b){return a<b?a=b,1:0;}
struct Graph{
int head[N],elen=1;
struct Edge{int nex,to,dis;}edge[N<<2];
void addedge(int u,int v,int w){
edge[++elen]=(Edge){head[u],v,w},head[u]=elen;
edge[++elen]=(Edge){head[v],u,w},head[v]=elen;
}
}E,G;//原图,三度化
vector<int>item[N],Edge[M];
int leaf[75][N],lc[M],rc[M];
int dep[2][N],siz[N],rot[75],p[N],mxd[2];
int id[N<<1][2][75],ori[N][4];
int dfn[M],low[M],stk[M],scc[M];
int n,m,tot,L,R,cnt,rt0,rt1,bd,mxp,S,tim,top;
int ll,rr,pos,op,k;//parameters of connect(l,r,rt,k)/connect(u,fath,o)
bool vis[N<<2],pd[M];
void rebuild(int u,int fath){
int f=0;
Go(E,u)if(v^fath){
if(!f)G.addedge(u,v,1),f=u;
else ++tot,G.addedge(f,tot,0),G.addedge(tot,v,1),f=tot;
rebuild(v,u);
}
}
void build(int l,int r,int& rt,int k){
if(!rt)rt=++S,id[rt][0][k]=S,id[rt][1][k]=S+K;
if(l==r)return leaf[k][l]=rt,void();
build(l,mid,ls,k),build(mid+1,r,rs,k);
Edge[id[rt][0][k]].emplace_back(id[ls][0][k]),Edge[id[rt][0][k]].emplace_back(id[rs][0][k]);
Edge[id[ls][1][k]].emplace_back(id[rt][1][k]),Edge[id[rs][1][k]].emplace_back(id[rt][1][k]);
}
void connect(int l,int r,int rt,int k){
if(ll<=l&&r<=rr){
if(op)Edge[id[rt][op][k]].emplace_back(pos);//区间连单点 Out连In
else Edge[pos].emplace_back(id[rt][op][k]);//单点连区间 Out连In
return ;
}
if(ll<=mid)connect(l,mid,ls,k);
if(mid<rr)connect(mid+1,r,rs,k);
}
void getroot(int u,int fath){
siz[u]=1;
Go(G,u)if(v!=fath&&!vis[e]){
getroot(v,u),siz[u]+=siz[v];
int now=max(siz[v],tot-siz[v]);
if(cmin(mxp,now))rt0=u,rt1=v,bd=e;
}
}
void getdep(int u,int fath,int op){
mxd[op]=max(mxd[op],dep[op][u]),p[++pos]=u;
Go(G,u)if(v!=fath&&!vis[e])
dep[op][v]=dep[op][u]+G.edge[e].dis,getdep(v,u,op);
}
void construct(int o){
//cnt 不选 cnt-1 选
for(int i=1;i<=pos;++i)
Edge[id[leaf[cnt-1][dep[o][p[pos]]]][0][cnt-1]].emplace_back(ori(p[pos],0)),
Edge[id[leaf[cnt-0][dep[o][p[pos]]]][0][cnt-0]].emplace_back(ori(p[pos],2)),
Edge[ori(p[pos],1)].emplace_back(id[leaf[cnt-1][dep[o][p[pos]]]][1][cnt-1]),
Edge[ori(p[pos],3)].emplace_back(id[leaf[cnt-1][dep[o][p[pos]]]][1][cnt-0]),
Edge[ori(p[pos],0)].emplace_back(ori(p[pos],1)),
Edge[ori(p[pos],2)].emplace_back(ori(p[pos],3));
}
void connect(int u,int fath,int o){//o 其他部分,k 其他部分的选择点
ll=max(0,L-dep[o^1][u]),rr=min(max(0,R-dep[o^1][u]),mxd[o]);
for(auto opt:item[u])
if(opt==0)pos=ori(u,3),op=0,connect(1,mxd[o],rot[k+1],k+1),pos=ori(u,0),op=1,connect(1,mxd[o],rot[k],k);
else if(opt==1)pos=ori(u,3),op=0,connect(1,mxd[o],rot[k],k),pos=ori(u,0),op=1,connect(1,mxd[o],rot[k+1],k+1);
else if(opt==2)pos=ori(u,1),op=0,connect(1,mxd[o],rot[k+1],k+1),pos=ori(u,2),op=1,connect(1,mxd[o],rot[k],k);
else if(opt==3)pos=ori(u,1),op=0,connect(1,mxd[o],rot[k],k),pos=ori(u,2),op=1,connect(1,mxd[o],rot[k+1],k+1);
Go(G,u)if(v!=fath&&!vis[e])connect(v,u,o);
}
void divide(int u){
if(tot==1)return ;
rt0=rt1=bd=0,mxp=1e9,getroot(u,0),vis[bd]=vis[bd^1]=true;
mxd[0]=pos=0,dep[0][rt0]=G.edge[bd].dis,getdep(rt0,0,0);
++cnt,build(1,mxd[0],rot[cnt],cnt),++cnt,build(1,mxd[0],rot[cnt],cnt);
construct(0);
// for(int i=1;i<=mxd;++i)
// Edge[id(leaf[cnt][i],0,cnt)].emplace_back(id(leaf[cnt][i],1,cnt));
mxd[1]=pos=0,dep[1][rt1]=G.edge[bd].dis,getdep(rt1,0,1);
++cnt,build(1,mxd[1],rot[cnt],cnt),++cnt,build(1,mxd[1],rot[cnt],cnt);
construct(1);
//cnt-3 左部分的选择点 cnt-2 左部分的不选择点 cnt-1 右部分的选择点 cnt 右部分的不选择点
k=cnt-1,connect(rt0,0,1),k=cnt-3,connect(rt1,0,0);
int tmprt=rt1,tmpsiz=siz[rt1];
tot=tot-siz[rt1],divide(rt0);
tot=tmpsiz,divide(tmprt);
}
void tarjan(int u){
dfn[u]=low[u]=++tim,pd[stk[++top]=u]=1;
for(auto v:Edge[u])
if(!dfn[v])tarjan(v),low[u]=min(low[u],low[v]);
else if(vis[v])low[u]=min(low[u],dfn[v]);
if(low[u]==dfn[u]){
scc[u]=++cnt,vis[u]=0;
while(stk[top]!=u)
scc[stk[top]]=cnt,vis[stk[top--]]=0;
top--;
}
}
signed main(){
n=read(),m=read(),L=read(),R=read();
for(int i=1,u,v;i<n;++i)
u=read(),v=read(),E.addedge(u,v,1);
for(int i=1,a,op;i<=m;++i)
a=read(),op=read(),item[a].emplace_back(op);
rebuild(1,0),divide(1);
for(int i=1;i<=S+(n<<2);++i)if(!dfn[i])tarjan(i);
for(int i=1;i<=n<<1;++i)
if(scc[i]==scc[i+(n<<1)])return puts("NO"),0;
return puts("YES"),0;
}
然后学习了 LibreOJ - 571 Misaka Network 与 Accelerator (边分治+线段树+2-SAT) - jrltx - 博客园 (cnblogs.com) 的优雅代码,并自己写了一发
为了处理每个点选和不选拆出的两种点,这里依旧是拆成了两颗线段树,不过写在了一起
即设 \(id[rt][0/1]\) 记录线段树上节点 \(rt\) 所对应的深度区间 全不选/全选 的情况的点
那么,每个结点和它的两个儿子连边 \(id[rt][0/1]\to id[ls][0/1],id[rt][0/1]\to id[rs][0/1]\)
\(ls,rs\) 表示左右孩子,这里的 \(\to\) 是按照 2-SAT 的方式连边,即同时连了 \((u,v)\) 和 \((!v,!u)\) (\(!u\) 表示 \(u\) 的反面)
对于当前边分治的分治中心边 \(bd\) 管辖范围内的每个结点 \(x\) (\(x\) 代表接入了该结点,\(!x\) 代表不接入该结点),设它到边 \(bd\) 的该侧端点的距离为 \(dep\),我们就把 \(x\) 和线段树上 \(dep\) 对应的叶子节点 \(rt\) 连边 \(id[rt][0]\to !x,id[rt][1]\to x\)
并与另一侧线段树上深度范围在 \([L-dep-edge[bd].dis,R-d-edge[bd].dis]\) 内的结点 \(rt\) 按题目给的要求连边 \(\{x\to id[rt][0],x\to id[rt][1],!x\to id[rt][0],!x\to id[rt][1]\}\)
最后跑2-SAT就行了,值得注意的是,这里不宜使用 tarjan 求强连通分量,因为要开 \(4\) 个数组,内存有点紧
这里就直接暴力枚举起点扫荡全图,如果扫到点 \(x\) 的时候发现 \(!x\) 也被扫过,那就 G 了
一个连通块只需要扫一次,所以这样是 \(O(n)\) 的
Code
#include<bits/stdc++.h>
#define ls lc[rt]
#define rs rc[rt]
#define mid ((l+r)>>1)
#define pii pair<int,int>
#define scan() for(int e=head[u],v;v=edge[e].to,~e;e=edge[e].nex)
#define scanG() for(int e=H[u],v;v=G[e].to,~e;e=G[e].nex)
using namespace std;
const int N = 2e5+5;
const int inf = 1e9;
char buf[1<<23],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read(){
int s=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))s=s*10+(ch^48),ch=getchar();
return s*w;
}
bool cmin(int &a,int b){return a>b?a=b,1:0;}
bool cmax(int &a,int b){return a<b?a=b,1:0;}
struct Two_Sat{
struct Edge{int nex,to;}edge[N*80];//
int head[N*40],stk[N*40],n,elen,top;
bool vis[N*40];
void init(int _n){
n=_n,elen=top=0;
for(int i=0;i<=(n<<1|1);++i)
vis[i]=false,head[i]=-1;
}
void add(int u,int v){edge[elen]=(Edge){head[u],v},head[u]=elen++;}
void addedge(int u,int v,int f,int g){add(u<<1|f,v<<1|g),add(v<<1|(!g),u<<1|(!f));}
//x<<1|1 选 x<<1 不选
bool dfs(int u){
if(vis[u^1])return false;
if(vis[u])return true;
vis[stk[top++]=u]=true;
scan()if(!dfs(v))return false;
return true;
}
bool check(){
// for(int i=0;i<elen;i+=2)cout<<edge[i].to<<" "<<edge[i^1].to<<'\n';
for(int i=1;i<=n;++i)vis[i]=false;
for(int i=1;i<=n;++i)
if(!vis[i<<1]&&!vis[i<<1|1]){
top=0;
if(!dfs(i<<1)){
while(top)vis[stk[--top]]=false;
if(!dfs(i<<1|1))return false;
}
}
return true;
}
}ts;
struct Segment_Tree{
int lc[N*40],rc[N*40],rot[N],id[N*40][2],ori[N],siz,siz2;
void init(int n){siz=siz2=0;for(int i=1;i<=n;++i)ori[i]=++siz2;}
void build(int l,int r,int& rt){
rt=++siz,ls=rs=0;
id[rt][0]=++siz2,id[rt][1]=++siz2;
if(l==r)return ;
build(l,mid,ls),build(mid+1,r,rs);
ts.addedge(id[rt][0],id[ls][0],1,1),ts.addedge(id[rt][0],id[rs][0],1,1);
ts.addedge(id[rt][1],id[ls][1],1,1),ts.addedge(id[rt][1],id[rs][1],1,1);
}
void connect1(int l,int r,int rt,int L,int R,int v,int f,int op){
if(L<=l&&r<=R)return ts.addedge(v,id[rt][op],f,1),void();
if(l<=R&&r>=L)connect1(l,mid,ls,L,R,v,f,op),connect1(mid+1,r,rs,L,R,v,f,op);
}
void connect2(int l,int r,int rt,int cur,int v){
if(l==r){
ts.addedge(id[rt][0],v,1,0);
ts.addedge(id[rt][1],v,1,1);
return ;
}
cur<=mid?connect2(l,mid,ls,cur,v):connect2(mid+1,r,rs,cur,v);
}
}seg;
struct Edge{int nex,to,dis;}edge[N],G[N];
int head[N],H[N],a[N],siz[N],dep[N],rot[N],Mxd[N];
int n,m,elen,glen,tot,bd,mxp,mxd,L,R;
bool vis[N];
void addedge(int u,int v){
edge[elen]=(Edge){head[u],v,0},head[u]=elen++;
edge[elen]=(Edge){head[v],u,0},head[v]=elen++;
}
void addedge(int u,int v,int w){
G[glen]=(Edge){H[u],v,w},H[u]=glen++;
G[glen]=(Edge){H[v],u,w},H[v]=glen++;
}
void rebuild(int u,int fath){
int f=0;
scan()if(v^fath){
if(!f)addedge(u,v,1),f=u;
else ++tot,addedge(f,tot,0),addedge(tot,v,1),f=tot;
rebuild(v,u);
}
}
void getroot(int u,int fath,int tot){
siz[u]=1;
scanG()if(v!=fath&&!vis[e]){
getroot(v,u,tot),siz[u]+=siz[v];
if(cmin(mxp,max(siz[v],tot-siz[v])))bd=e;
}
}
void getdep(int u,int fath){
mxd=max(mxd,dep[u]);
scanG()if(v!=fath&&!vis[e])
dep[v]=dep[u]+G[e].dis,getdep(v,u);
}
void construct(int u,int fath,int k){
seg.connect2(0,Mxd[k],rot[k],dep[u],seg.ori[u]);
for(int i=0;i<=3;++i)if(a[u]>>i&1)
seg.connect1(0,Mxd[k^1],rot[k^1],L-dep[u]-G[k^1].dis,R-dep[u]-G[k^1].dis,seg.ori[u],i>>1&1,i&1);
scanG()if(v!=fath&&!vis[e])construct(v,u,k);
}
void divide(int u,int tot){
if(tot==1)return;
mxp=inf,getroot(u,0,tot);
vis[bd]=vis[bd^1]=true;
mxd=dep[G[bd].to]=0,getdep(G[bd].to,0),seg.build(0,Mxd[bd]=mxd,rot[bd]);
mxd=dep[G[bd^1].to]=0,getdep(G[bd^1].to,0),seg.build(0,Mxd[bd^1]=mxd,rot[bd^1]);
construct(G[bd].to,0,bd),construct(G[bd^1].to,0,bd^1);
int a=siz[G[bd].to],b=tot-siz[G[bd].to],t=bd;
divide(G[t].to,a),divide(G[t^1].to,b);
}
signed main(){
memset(head,-1,sizeof(head)),memset(H,-1,sizeof(H));
n=tot=read(),m=read(),L=read(),R=read();
for(int i=1,u,v;i<n;++i)u=read(),v=read(),addedge(u,v);
for(int i=1,x,f;i<=m;++i)x=read(),f=read(),a[x]|=1<<f;
rebuild(1,0),ts.init(tot*30),seg.init(tot),divide(1,tot);
puts(ts.check()?"YES":"NO");
return 0;
}