【题目】
HDU
给定一幅 n n n个点 m m m条边的无向图以及这个无向图的一棵生成树,非树边的 l c a lca lca均为 1 1 1。
求一个最小的割满足割去恰好两条树边,多组数据。
n ≤ 2 × 1 0 4 , m ≤ 1 0 5 , T ≤ 25 n\leq 2\times 10^4,m\leq 10^5,T\leq 25 n≤2×104,m≤105,T≤25
【解题思路】
割树边的方式有两种情况:割了两条存在祖先关系的,或割了两条没有祖先关系的。
令 d x d_x dx表示一个端点落在 x x x子树内所有非树边条数。
那么对于第一种情况,设答案删的是 x x x的父边和 y y y的父边,答案显然就是 d x − d y d_x-d_y dx−dy,那么 x x x固定时 y y y是 x x x的儿子显然是最优的。这部分直接枚举即可,复杂度是 O ( n ) O(n) O(n)的。
对于第二种情况,我们令 f x , y f_{x,y} fx,y表示一个端点在 x x x子树内,一个在 y y y子树内边的数量,答案就是 d x − d y + 2 ⋅ f x , y d_x-d_y+2\cdot f_{x,y} dx−dy+2⋅fx,y。
考虑在搜索整颗树的时候用数据结构来维护对于当前点 x x x的每个点的 d y + 2 ⋅ f x , y d_y+2\cdot f_{x,y} dy+2⋅fx,y。
首先我们需要把 x x x子树的 f f f进行合并,对于一条端点恰好为 x x x的非树边 ( x , v ) (x,v) (x,v),需要把 v v v到根路径上的 − d + 2 f -d+2f −d+2f都减去 2 2 2。
这里我们显然可以用树链剖分+线段树来实现。
但若是直接做的话,时间复杂度和空间复杂度都是 O ( m log 2 n ) O(m\log ^2 n) O(mlog2n)的,空间上只有 64 MB 64\text{MB} 64MB并不能接受。
不妨采用 DSU ON TREE \text{DSU ON TREE} DSU ON TREE的思想, DFS \text{DFS} DFS的时候先进入重儿子,始终保留重儿子的线段树。对于轻儿子线段树在依次合并时回收空闲节点。那这样 DFS \text{DFS} DFS到一个点的时候,需要的线段树个数就是这个点到根路径上轻边条数。
实现上的话,可以采用标记永久化,当然标记下放也是一样的。那么我们可以先按 DFS \text{DFS} DFS序建出一棵原始的线段树,这样只需要在每个节点上打个 t a g tag tag就可以了。
于是这样做空间复杂度就是 O ( n log n ) O(n\log n) O(nlogn)的了。
【参考代码】
#include
using namespace std;
const int N=2e4+10,M=2e5+10,K=M*6;
int n,m,ind,ans,a[N],d[N],rt[N];
int top[N],siz[N],fa[N],son[N],pos[N];
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
void gmin(int &x,int y){x=min(x,y);}
namespace Segment
{
#define lc (x<<1)
#define rc (x<<1|1)
queue<int>q;
struct Seg
{
int w[K],ls[K],rs[K],tar[K],val[K];
void build(int x,int l,int r)
{
if(l==r){w[x]=a[l];return;}
int mid=(l+r)>>1;
build(lc,l,mid);build(rc,mid+1,r);
w[x]=min(w[lc],w[rc]);
}
int newnode(int x){int y=q.front();q.pop();ls[y]=rs[y]=tar[y]=0;val[y]=w[x];return y;}
void pushup(int y,int x)
{
if(!ls[y]) ls[y]=newnode(lc);
if(!rs[y]) rs[y]=newnode(rc);
val[y]=min(val[ls[y]],val[rs[y]]);
}
void pushdown(int y,int x)
{
if(tar[y])
{
if(!ls[y]) ls[y]=newnode(lc);
if(!rs[y]) rs[y]=newnode(rc);
tar[ls[y]]+=tar[y];val[ls[y]]+=tar[y];tar[rs[y]]+=tar[y];val[rs[y]]+=tar[y];
tar[y]=0;
}
}
void update(int &y,int x,int l,int r,int L,int R,int v)
{
if(!y) y=newnode(x);
if(L<=l && r<=R) {tar[y]+=v;val[y]+=v;return;}
pushdown(y,x);
int mid=(l+r)>>1;
if(L<=mid) update(ls[y],lc,l,mid,L,R,v);
if(R>mid) update(rs[y],rc,mid+1,r,L,R,v);
pushup(y,x);
}
void upchain(int &x,int y)
{
while(top[y]^1) update(x,1,1,n,pos[top[y]],pos[y],-2),y=fa[top[y]];
if(y>1) update(x,1,1,n,2,pos[y],-2);
}
int merge(int y,int z,int x,int l,int r)
{
if(!y || !z) return y+z;
if(l==r) tar[y]+=tar[z];
else
{
pushdown(y,x);pushdown(z,x);
int mid=(l+r)>>1;
ls[y]=merge(ls[y],ls[z],lc,l,mid);
rs[y]=merge(rs[y],rs[z],rc,mid+1,r);
pushup(y,x);
}
q.push(z);return y;
}
void clear(int x,int l,int r)
{
if(!x) return; q.push(x);
if(l==r) return;
int mid=(l+r)>>1;
clear(ls[x],l,mid);clear(rs[x],mid+1,r);//wrong because write lc,rc
}
}tr;
#undef lc
#undef rc
}
using namespace Segment;
namespace Graph
{
struct Tway{int v,nex;};
struct Gra
{
Tway e[M];int tot,head[N];
void add(int u,int v)
{
e[++tot]=(Tway){v,head[u]};head[u]=tot;
e[++tot]=(Tway){u,head[v]};head[v]=tot;
}
void clear(){tot=0;for(int i=0;i<=n;++i)head[i]=0;}
}T,G;
void dfs1(int x)
{
siz[x]=1;
for(int i=T.head[x];i;i=T.e[i].nex)
{
int v=T.e[i].v;
if(v==fa[x]) continue;
fa[v]=x;dfs1(v);siz[x]+=siz[v];d[x]+=d[v];
if(siz[v]>siz[son[x]]) son[x]=v;//wrong because write siz[x]>..
}
if(x>1)
{
for(int i=T.head[x];i;i=T.e[i].nex)
if(T.e[i].v^fa[x]) gmin(ans,d[x]-d[T.e[i].v]);
}
}
void dfs2(int x,int tp)
{
pos[x]=++ind;top[x]=tp;
if(son[x]) dfs2(son[x],tp);
for(int i=T.head[x];i;i=T.e[i].nex)
{
int v=T.e[i].v;
if(v==fa[x] || v==son[x]) continue;
dfs2(v,v);
}
}
void dfs3(int x)
{
if(son[x]) dfs3(son[x]),rt[x]=rt[son[x]];
for(int i=T.head[x];i;i=T.e[i].nex)
{
int v=T.e[i].v;
if(v==fa[x] || v==son[x]) continue;
dfs3(v);rt[x]=tr.merge(rt[x],rt[v],1,1,n);//wrong because forgot to write dfs3
}
if(x==1) return;
for(int i=G.head[x];i;i=G.e[i].nex) tr.upchain(rt[x],G.e[i].v);
tr.update(rt[x],1,1,n,pos[x],pos[x],M);gmin(ans,d[x]+tr.val[rt[x]]);tr.update(rt[x],1,1,n,pos[x],pos[x],-M);
}
}
using namespace Graph;
namespace DreamLolita
{
void clear()
{
T.clear();G.clear();tr.clear(rt[1],1,n);
for(int i=0;i<=n;++i) fa[i]=siz[i]=son[i]=rt[i]=d[i]=top[i]=0;
ind=0;
}
void init()
{
n=read();m=read();ans=M;
for(int i=1;i<n;++i) T.add(read(),read());
for(int i=n,x,y;i<=m;++i) d[x=read()]++,d[y=read()]++,G.add(x,y);
}
void solution(int cas)
{
init();dfs1(1);dfs2(1,1);
for(int i=1;i<=n;++i) a[pos[i]]=d[i];
tr.build(1,1,n);dfs3(1);
printf("Case #%d: %d\n",cas,ans+2);
clear();
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("HDU5511.in","r",stdin);
freopen("HDU5511.out","w",stdout);
#endif
int T=read();for(int i=1;i<K;++i) q.push(i);
for(int i=1;i<=T;++i) DreamLolita::solution(i);
return 0;
}