P5327 [ZJOI2019]语言
题目描述
简要题意:给定一棵树和一些链,问树上处于同一条链的不同点对数。
Solution
对于每一个点,考虑以它为端点的可行路径有哪些。
我们可以发现,可以到达的节点会组成一个斯坦纳树,这棵斯坦纳树包含了即经过链。
我们进一步可知,这棵斯坦纳树就是以和经过的所有链的端点为关键点的最小斯坦纳树。
所以我们考虑对于每一个点,维护经过它的链组成的斯坦纳树是什么,顺便维护答案(斯坦纳树的边数)。
事实上,斯坦纳树的边数就是其中所有点的深度-dfs序相邻的点的LCA的深度,如下:
设斯坦纳树的点集为,记的dfs序为 ,深度为 。
若满足 ,即dfs序按升序排序,则
可以用线段树合并(启发式合并也可)支持上述信息的维护。
线段树的下标为 ,并记录 中最大的点, 最小的点,以及 。
合并时 。
统计答案时
因此,对于每一条路径 ,我们可以用树上差分把它拆成 这四条代价分别为+1,+1,-1,-1的链,分别放入权值线段树,并不断向上合并到根,沿路统计答案即可。
表实现LCA,时间复杂度 。
如果写倍增LCA的会多一个,卡卡常应该也能过。
PS:这道题写了1h,调了5h,原因是线段树动态开点要开log倍的内存。但我一直以为是dfs爆栈了(因为今早有一题先例),由于要用到dfs序,所以甚至在try coding 手工栈,However,WA声依旧,改得代码奇丑无比,自闭了一个下午,最后柳暗花明又一村,有一种想右转直行的冲动(五楼机房右边是窗)。
Code
最后贴一个丑陋的代码(早把罪恶的手工栈删了)。
#include<bits/stdc++.h>
#define RG register
typedef long long ll;
const int MAXN=4e5+50;
std::vector<int> e[MAXN],Del[MAXN];
int DFN=0,t=0,n,m;
int dfn[MAXN],st[MAXN][21],fa[MAXN];
int pre[MAXN],rt[MAXN],Log[MAXN];
ll dep[MAXN],ans=0;
inline int read()
{
RG int x=0,f=1; char c=getchar();
while (c<'0'||c>'9') { if(c=='-') f=-1; c=getchar(); }
while (c>='0'&&c<='9') { x=(x<<3)+(x<<1)+(c^48); c=getchar(); }
return x*f;
}
inline void get_dfn(int x,int father)
{
dfn[x]=++DFN;
pre[x]=++t; st[t][0]=x;
fa[x]=father; dep[x]=dep[father]+1;
for (RG int i=0;i<e[x].size();i++)
if (e[x][i]!=fa[x]) get_dfn(e[x][i],x),st[++t][0]=x;
}
inline void get_st()
{
Log[1]=0;
for (RG int i=2;i<=t;i++) Log[i]=Log[i>>1]+1;
for (RG int i=1;i<=Log[t];i++)
for (RG int j=1;j<=t-(1<<i)+1;j++)
st[j][i]=dep[st[j][i-1]]<dep[st[j+(1<<(i-1))][i-1]]?st[j][i-1]:st[j+(1<<(i-1))][i-1];
}
inline int get_lca(int x,int y)
{
x=pre[x],y=pre[y];
if (x>y) std::swap(x,y);
RG int k=Log[y-x+1];
return std::max(1,dep[st[x][k]]<dep[st[y-(1<<k)+1][k]]?st[x][k]:st[y-(1<<k)+1][k]);
}
struct Segment_Tree
{
int segnum=0;
struct treenode{int lson,rson,fi,la; ll sum,ans; } tree[MAXN<<4];
#define lft tree[x].lson
#define rht tree[x].rson
inline void up(int x)
{
RG int lca=get_lca(tree[lft].la,tree[rht].fi);
//cout<<lft<<" "<<rht<<" "<<lca<<endl;
tree[x].ans=tree[lft].ans+tree[rht].ans-dep[lca];
tree[x].fi=tree[lft].fi?tree[lft].fi:tree[rht].fi;
tree[x].la=tree[rht].la?tree[rht].la:tree[lft].la;
}
inline void change(int &x,int p,int v,int L,int R)
{
if (!x) x=++segnum;
if (L==R)
{
tree[x].sum+=v;
tree[x].ans=tree[x].sum?dep[p]:0;
tree[x].la=tree[x].fi=tree[x].sum?p:0;
return;
}
RG int mid=(L+R)>>1;
if (dfn[p]<=mid) change(lft,p,v,L,mid);
else change(rht,p,v,mid+1,R);
up(x);
}
inline ll query(int x)
{
RG int lca=get_lca(tree[x].la,tree[x].fi);
return tree[x].ans-dep[lca];
}
inline void merge(int &x,int v,int L,int R)
{
if (!x||!v) { x|=v; return; }
if (L==R)
{
tree[x].sum+=tree[v].sum;
tree[x].ans|=tree[v].ans;
tree[x].la|=tree[v].la;
tree[x].fi|=tree[v].fi;
return;
}
RG int mid=(L+R)>>1;
merge(lft,tree[v].lson,L,mid);
merge(rht,tree[v].rson,mid+1,R);
up(x);
}
} segment;
inline void solve(int x)
{
for (RG int i=0;i<e[x].size();i++)
if (e[x][i]!=fa[x]) solve(e[x][i]);
for (RG int i=0;i<Del[x].size();i++) segment.change(rt[x],Del[x][i],-1,1,n);
ans+=segment.query(rt[x]);
//cout<<ans<<endl;
if (fa[x]) segment.merge(rt[fa[x]],rt[x],1,n);
}
int main()
{
//freopen("a.in","r",stdin);
n=read(),m=read();
for (RG int i=1;i<n;i++)
{
int u=read(),v=read();
e[u].push_back(v);
e[v].push_back(u);
}
dep[0]=-1;
get_dfn(1,0);
get_st();
for (RG int i=1;i<=m;i++)
{
RG int u=read(),v=read(),lca=get_lca(u,v);
//cout<<lca<<endl;
segment.change(rt[u],v,1,1,n); segment.change(rt[v],u,1,1,n);
segment.change(rt[v],v,1,1,n); segment.change(rt[u],u,1,1,n);
//segment.change(rt[lca],u,-1,1,n); segment.change(rt[lca],v,-1,1,n);
//if (fa[lca]) segment.change(rt[fa[lca]],u,-1,1,n),segment.change(rt[fa[lca]],v,-1,1,n);
//cout<<lca<<endl;
Del[lca].push_back(u),Del[lca].push_back(v);
if (fa[lca]) Del[fa[lca]].push_back(u),Del[fa[lca]].push_back(v);
}
solve(1);
printf("%lld\n",ans>>1);
return 0;
}
/*
12 4
1 2
2 3
2 4
4 5
1 6
6 7
6 8
6 9
9 10
9 11
1 12
4 12
8 10
7 11
3 10
*/
还附加了一个小样例,然而样例输出咕了。