https://www.luogu.org/problemnew/show/P5327
最可做的題結果場上根本沒想打了60分,後來聽說想寫正解的都被卡了......做法:考慮對一個點x來說,可以到達它的點一定構成一個樹上包含x的連通塊,而連通塊裏距離x點最遠的點一定是跨過這個點的路徑的端點,發現這個連通塊的求法可以用類似虛樹的方法做,把所有跨過該點的路徑的端點按dfs序排序,先假設根會在連通塊裏,那麼發現按dfs序一個個加入點,假設上一次加了點p,這一次加入q,q產生的貢獻就是dep(q)-dep(lca(p,q))【可畫圖理解】,最後因爲根是假設在裏面的,連通塊的要減去dep(所有點lca)。知道對一個點求能到達的點的數量後,考慮如何對所有點統計。用線段樹維護按dfs序排序的點的選取情況,發現[l,mid]子樹和[mid+1,r]子樹合併時只要知道子樹構成連通塊dfs序最小最大的兩個點是什麼就可以按之前的方法O(1)合併了。而一段路徑能產生的貢獻可以用樹上差分來搞,假設一條u到v的路徑,可以在u,v處各加入u,v,在lca處統計完後減兩個u兩個v,那麼上線段樹合併即可。
代碼:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+100;
ll ans=0;
int n,m,dep[N],dfn[N],idx[N],tim;
vector<int>mp[N],add[N],del[N];
namespace LCA{
int fi[N],ol[3*N],mn[3*N][21],wh[3*N][21],lg[3*N];
void dfs(int x,int fa)
{
fi[x]=++tim,ol[tim]=x;
for(int i=0,v;i<mp[x].size();i++)
{
v=mp[x][i];
if(v==fa)continue;
dep[v]=dep[x]+1,dfs(v,x),ol[++tim]=x;
}
}
void init()
{
dep[1]=1,tim=0,dfs(1,0);
lg[1]=0;
for(int i=2;i<=tim;i++)lg[i]=lg[i>>1]+1;
for(int i=1;i<=tim;i++)wh[i][0]=ol[i],mn[i][0]=dep[ol[i]];
for(int i=1,len=2,md;i<=20;i++,len<<=1)
{
md=len>>1;
for(int j=1;j+len-1<=tim;j++)
{
if(mn[j][i-1]<=mn[j+md][i-1])wh[j][i]=wh[j][i-1];
else wh[j][i]=wh[j+md][i-1];
mn[j][i]=dep[wh[j][i]];
}
}
}
int lca(int u,int v)
{
u=fi[u],v=fi[v];
if(u>v)swap(u,v);
int tmp=lg[v-u+1],len=1<<tmp;
if(mn[u][tmp]<=mn[v-len+1][tmp])return wh[u][tmp];
else return wh[v-len+1][tmp];
}
}
namespace SEG{
struct gg{
int sz,ld,rd,ls,rs,val;
}seg[N*40];int snum,rt[N];
gg mg_nd(gg x,gg y)
{
gg res;int tmp=LCA::lca(x.rd,y.ld);
res.sz=x.sz+y.sz-dep[tmp];
res.ld=x.ld,res.rd=y.rd;
return res;
}
void push_up(int nw)
{
int ls=seg[nw].ls,rs=seg[nw].rs;
seg[nw].sz=seg[nw].ld=seg[nw].rs=0;
if(seg[ls].sz)seg[nw]=seg[ls];
if(seg[rs].sz)
{
if(seg[nw].sz)seg[nw]=mg_nd(seg[nw],seg[rs]);
else seg[nw]=seg[rs];
}
seg[nw].ls=ls,seg[nw].rs=rs;
}
void leaf(int x,int ps)
{
if(seg[x].val)seg[x].sz=dep[ps];
else seg[x].sz=0;
seg[x].ld=seg[x].rd=ps;
}
void cg(int &nw,int to,int l,int r,int w)
{
if(!nw)nw=++snum;
if(l==r)seg[nw].val+=w,leaf(nw,idx[l]);
else
{
int mid=(l+r)>>1;
if(to<=mid)cg(seg[nw].ls,to,l,mid,w);
else cg(seg[nw].rs,to,mid+1,r,w);
push_up(nw);
}
}
int mg_seg(int x,int y,int l,int r)
{
if(!x||!y)return x+y;
if(l==r)
{
seg[x].val+=seg[y].val,leaf(x,idx[l]);
return x;
}
int mid=(l+r)>>1;
seg[x].ls=mg_seg(seg[x].ls,seg[y].ls,l,mid);
seg[x].rs=mg_seg(seg[x].rs,seg[y].rs,mid+1,r);
push_up(x);
return x;
}
int ask(int x)
{return seg[x].sz-dep[LCA::lca(seg[x].ld,seg[x].rd)];}
}
using namespace SEG;
void dfs0(int x,int fa)
{
dfn[x]=++tim,idx[tim]=x;
for(int v,i=0;i<mp[x].size();i++)
{
v=mp[x][i];
if(v==fa)continue;
dfs0(v,x);
}
}
void dfs1(int x,int fa)
{
for(int v,i=0;i<mp[x].size();i++)
{
v=mp[x][i];
if(v==fa)continue;
dfs1(v,x),rt[x]=mg_seg(rt[x],rt[v],1,tim);
}
for(int i=0;i<add[x].size();i++)
{
cg(rt[x],dfn[add[x][i]],1,tim,1);
}
ans+=max(0,ask(rt[x]));
for(int i=0;i<del[x].size();i++)
cg(rt[x],dfn[del[x][i]],1,tim,-2);
}
int main()
{
scanf("%d%d",&n,&m);
for(int u,v,i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
mp[u].push_back(v);
mp[v].push_back(u);
}
LCA::init(),tim=0,dfs0(1,0);
for(int i=1,u,v,lca;i<=m;i++)
{
scanf("%d%d",&u,&v);
lca=LCA::lca(u,v);
add[u].push_back(u),add[u].push_back(v);
add[v].push_back(u),add[v].push_back(v);
del[lca].push_back(u),del[lca].push_back(v);
}
dfs1(1,0);
printf("%lld\n",ans/2);
}