现在要求一个树上点集生成树的边数。
很显然,我们把点按 DFS 序排序后就是相邻两个点的距离加起来除以二了,因为 DFS 的时候每条边恰好走了两遍。
然后你就可以解决 SDOI2015 寻宝游戏了,只需要用 std::set
维护宝藏点,然后查询前驱后继计算距离增量即可。
一个麻烦一点的题:ZJOI2019 语言。
稍微手玩一下就可以发现我们钦定一个端点后就变成了分别求经过每个点的路径并的大小,也就是一条路径会造成其上所有点对应的生成树多出来两个端点,所以我们用类似雨天的尾巴的做法,对路径树上差分再线段树合并。容易发现上面那个 DFS 序排序后的距离和很容易搬到线段树上维护,只需要多记录每个节点的最小和最大 DFS 序,合并节点时计算上增加的距离即可。
注意本题可能卡常,建议使用 \(O(n\log n)-O(1)\) LCA。
此处为方便理解给出线段树和主函数部分代码。注意由于我们钦定了一个端点,实际求得的是无序对,答案需要除以二:
struct Node {
int l,r,mn,mx,w,cnt;
}tr[N*50];
int rt[N],tot,bin[N*50],tp;
il void Pushup(int k){
tr[k].mn=tr[ls].mn?tr[ls].mn:tr[rs].mn;
tr[k].mx=tr[rs].mx?tr[rs].mx:tr[ls].mx;
tr[k].w=tr[ls].w+tr[rs].w;
if(tr[ls].mx&&tr[rs].mn){
tr[k].w+=Dis(id[tr[ls].mx],id[tr[rs].mn]);
}
}
void Modify(int &k,int pos,int v,int l=1,int r=tim){
if(!k)k=tp?bin[tp--]:++tot;
if(l==r){
tr[k].cnt+=v,tr[k].w=0;
tr[k].mn=tr[k].mx=tr[k].cnt?l:0;return;
}
if(pos<=nmid)Modify(ls,pos,v,l,nmid);
else Modify(rs,pos,v,nmid+1,r);
Pushup(k);
}
int Merge(int u,int v,int l=1,int r=tim){
if(!u||!v)return u+v;
if(l==r){
tr[u].cnt+=tr[v].cnt,tr[u].mn=tr[u].mx=tr[u].cnt?l:0;
return u;
}
tr[u].l=Merge(tr[u].l,tr[v].l,l,nmid);
tr[u].r=Merge(tr[u].r,tr[v].r,nmid+1,r);
bin[++tp]=v,tr[v].mn=tr[v].mx=tr[v].w=tr[v].cnt=0;
tr[v].l=tr[v].r=0;
return Pushup(u),u;
}
LL ans;
void Calc(int u){
for(int i=hd[u];i;i=e[i].nxt){
int v=e[i].to;
if(v!=f[0][u])Calc(v),rt[u]=Merge(rt[u],rt[v]);
}
int r=rt[u];
if(tr[r].mn&&tr[r].mx&&tr[r].mn!=tr[r].mx){
ans+=tr[r].w+Dis(id[tr[r].mn],id[tr[r].mx]);
}
}
signed main(){
Read(n),Read(m);
for(int i=1,u,v;i<n;i++){
Read(u),Read(v),ade(u,v),ade(v,u);
}
DFS(1,0),Init();
for(int i=1,u,v,l;i<=m;i++){
Read(u),Read(v),l=LCA(u,v);int dfu=dfn[u],dfv=dfn[v];
Modify(rt[u],dfu,1),Modify(rt[v],dfu,1);
Modify(rt[u],dfv,1),Modify(rt[v],dfv,1);
Modify(rt[l],dfu,-1),Modify(rt[l],dfv,-1);
if(f[0][l])Modify(rt[f[0][l]],dfu,-1);
if(f[0][l])Modify(rt[f[0][l]],dfv,-1);
}
Calc(1),printf("%lld\n",ans>>2);
KafuuChino HotoKokoa
}
这两道题都很简单,考察的主要是选手对于简单结论的应用。