jzoj3667 【HNOI2014】世界树(worldtree) (虚树上dp)

86 篇文章 0 订阅
21 篇文章 0 订阅

这里写图片描述
这里写图片描述

虚树

对于若干个点可以在原树的基础上建出虚树,其实就是原树的浓缩,去掉了无用节点。 此题多次询问,但m之和与n同阶,适合使用虚树。
建一次虚树的时间与虚树点数有关,而不与原树点数相关。 (所以要注意数组清零之类的东西啊喵喵)

虚树的合成方法:

  • 首先求出所有虚树上的点:m个给出点与每两个给出点的lca.
    lca其实最多m个,考虑将m个点按照原树dfs序排序。 假如顺序为x,y,z,则lca(x,z)必定是lca(x,y)与lca(y,z)中的一个。画个图就知道了。因此只需要将排序后的m个点相邻求lca。

  • 然后考虑连边,类似于建笛卡尔树。 按dfs序,建一个栈,从上到下维护一条链。 当新插入点不在栈顶的子树中,则将栈顶与栈顶前一个连边,出栈。 感受一下这种方法。正确性是很显然的
    最后栈中需要一一连边。
    这样就得到虚树了,数组清零之类的细节自己考虑一下参考标程

然后这题的话有虚树就很简单了,先求出每个点最近的给出点,然后一条边两边如果都是同样的给出点控制,则这条边上也是给出点控制。 否则倍增出一个mid点,分两边计算即可。 时间复杂度 O(mlogm+mlognlca)
计算贡献时注意考虑不在虚树中的点。

据说有些题不需要连虚树边,得到一个序就行。。。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
const int N=3e5+10,inf=1e9;
int n,m;
int final[N],to[N*2],nex[N*2],tot,att[N],ans[N],has[N];
int g[N][19],dep[N],no[N],R[N],stm,gsz[N];
void link(int x,int y) {to[++tot]=y,nex[tot]=final[x],final[x]=tot;}

int sz[N];
void init_dfs(int x) {
    no[x]=++stm; for (int i=1; i<19; i++) g[x][i]=g[g[x][i-1]][i-1];
    for (int i=final[x]; i; i=nex[i]) if (to[i]!=g[x][0]) {
        int y=to[i]; dep[y]=dep[x]+1,g[y][0]=x;
        init_dfs(to[i]);
        sz[x]+=sz[to[i]];
    } R[x]=stm; sz[x]++;
}
int lca(int x,int y) {
    if (dep[x]<dep[y]) swap(x,y);
    for (int i=18;i>=0;i--) if (dep[g[x][i]]>=dep[y])x=g[x][i];
    if (x==y) return x;
    for (int i=18;i>=0;i--) if (g[x][i]!=g[y][i])x=g[x][i],y=g[y][i];
    return g[x][0];
}

int h[N],iskey[N],mm,ctag[N];
int final2[N],to2[N],nex2[N],w[N],tot2;
bool cmp(int x,int y) {return no[x]<no[y];}
int rnd;
bool reset(int x);
int jump(int x,int y) {
    for (int i=18;i>=0;i--)if(dep[g[y][i]]>dep[x])y=g[y][i];
    return y;
}
void link2(int x,int y) {
    to2[++tot2]=y,nex2[tot2]=final2[x],final2[x]=tot2,w[tot2]=dep[y]-dep[x];
    gsz[x]-=sz[jump(x,y)];
}
#define TOPS (S[S[0]])
#define LTOPS (S[S[0]-1])
int S[N],hasone;
void vTree() {
    mm=m; ++rnd; if (!hasone) h[++mm]=1;
    sort(h+1,h+1+mm,cmp); tot2=0;
    for (int i=1; i<=mm; i++) reset(h[i]);
    for (int i=2; i<=mm; i++) {
        int l=lca(h[i-1],h[i]);
        if (reset(l)) h[++mm]=l;
    }
    sort(h+1,h+1+mm,cmp);
    S[S[0]=1]=h[1]; for (int i=2; i<=mm; i++) {
        int x=h[i];
        while (S[0] && no[x] < no[TOPS] || no[x] > R[TOPS]) link2(LTOPS,TOPS),S[0]--;
        S[++S[0]]=x;
    }
    while (S[0]>1) link2(LTOPS,TOPS),S[0]--;
}

int f[N],e[N];
void dfsf(int x) {
    if (iskey[x]==rnd) f[x]=0,e[x]=x;
    for (int i=final2[x];i;i=nex2[i]) {
        dfsf(to2[i]); if (f[to2[i]]+w[i]<f[x]){
            f[x]=f[to2[i]]+w[i],e[x]=e[to2[i]];
        } else if (f[to2[i]]+w[i]==f[x]&&e[x]>e[to2[i]]) e[x]=e[to2[i]];
    }
}
void dfsup(int x,int uf,int ue) {
    if (uf<f[x] || uf==f[x]&&ue<e[x]) e[x]=ue;
    f[x]=min(uf,f[x]);
    int cf=inf,ce=0; 
    for (int i=final2[x];i;i=nex2[i]) if(f[to2[i]]+w[i]!=f[x] || e[x]!=e[to2[i]]) {
        if (f[to2[i]]+w[i]<cf) cf=f[to2[i]]+w[i],ce=e[to2[i]];
        else if (f[to2[i]]+w[i]==cf && ce>e[to2[i]]) ce=e[to2[i]];
    }
    for (int i=final2[x];i;i=nex2[i]){
        if (f[to2[i]]+w[i]!=f[x] || e[x]!=e[to2[i]]) dfsup(to2[i],f[x]+w[i],e[x]);
        else dfsup(to2[i],cf+w[i],ce);
    }
}
bool reset(int x) {
    if (ctag[x]==rnd) return 0; ctag[x]=rnd;
    final2[x]=0; f[x]=inf; gsz[x]=sz[x];
    return 1;
}
void gg(int x) {
    for (int z=final2[x];z;z=nex2[z]){
        int y=to2[z];
        if (e[x]==e[y]) ans[att[e[x]]]+=sz[jump(x,y)]-sz[y]+gsz[y];
        else {
            int mid=y;
            for (int i=18;i>=0;i--) {
                int r=g[mid][i];
                //mid是最后一个Y优秀点 
                int dx=f[x]+dep[r]-dep[x],dy=f[y]+dep[y]-dep[r];
                if(dx>dy||dx==dy&&e[x]>e[y]) mid=r;
            }
            if (g[mid][0]!=x) ans[att[e[x]]]+=sz[jump(x,g[mid][0])]-sz[mid];
            ans[att[e[y]]]+=sz[mid]-sz[y]+gsz[y];
        }
        gg(y);
    }
}
int main() {
    freopen("worldtree.in","r",stdin);
    freopen("worldtree.out","w",stdout);
    cin>>n; for (int i=1; i<n; i++) {
        int x,y; scanf("%d %d",&x,&y);
        link(x,y),link(y,x);
    }
    dep[1]=1,init_dfs(1);
    int q; cin>>q; for (int t=1; t<=q; t++) {
        hasone=0;
        scanf("%d",&m); for (int i=1; i<=m; i++) scanf("%d",&h[i]),att[h[i]]=i,ans[i]=0,hasone|=(h[i]==1),iskey[h[i]]=rnd+1;
        vTree();
        dfsf(1); dfsup(1,inf,0);
        gg(1);
        for (int i=final[1];i;i=nex[i]) has[to[i]]=0;
        for (int i=final2[1];i;i=nex2[i]) has[jump(1,to2[i])]=1;
        for (int i=final[1];i;i=nex[i]) if (!has[to[i]]) ans[att[e[1]]]+=sz[to[i]];
        ans[att[e[1]]]++; //calc node 1
        for (int i=1; i<=m; i++) printf("%d ",ans[i]); printf("\n");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值