BZOJ 3572: [Hnoi2014]世界树 [虚树 DP 倍增]

传送门

题意:

一棵树,多次询问,给出$m$个点,求有几个点到给定点最近



写了一晚上...

当然要建虚树了,但是怎么$DP$啊

大爷题解传送门

我们先求出到虚树上某个点最近的关键点

然后枚举所有的边$(f,x)$,讨论一下边上的点的子树应该靠谁更近

倍增求出分界点

注意有些没出现在虚树上的子树

注意讨论的时候只讨论链上的不包括端点,否则$f$的子树会被贡献多次

学到的一些$trick:$

1.$pair$的妙用

2.不需要建出虚树只要求虚树的$dfs$序(拓扑序)和$fa$就可以$DP$了

注意$DP$的时候必须先用儿子更新父亲再用父亲更新儿子,因为父亲的最优值有可能在其他儿子

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
#define pii pair<int,int>
#define MP make_pair
#define fir first
#define sec second
typedef long long ll;
const int N=3e5+5,INF=1e9;
inline int read(){
    char c=getchar();int x=0,f=1;
    while(c<‘0‘||c>‘9‘){if(c==‘-‘)f=-1;c=getchar();}
    while(c>=‘0‘&&c<=‘9‘){x=x*10+c-‘0‘;c=getchar();}
    return x*f;
}

int n,Q;
struct Edge{
    int v,ne,w;
}e[N<<1];
int cnt,h[N];
inline void ins(int u,int v){
    cnt++;
    e[cnt].v=v;e[cnt].ne=h[u];h[u]=cnt;
    cnt++;
    e[cnt].v=u;e[cnt].ne=h[v];h[v]=cnt;
}
int fa[N][20],deep[N],dfn[N],dfc,size[N],All;
void dfs(int u){
    dfn[u]=++dfc;
    size[u]=1;
    for(int i=1;(1<<i)<=deep[u];i++)
        fa[u][i]=fa[ fa[u][i-1] ][i-1];
    for(int i=h[u];i;i=e[i].ne)
        if(e[i].v!=fa[u][0]){
            fa[e[i].v][0]=u;
            deep[e[i].v]=deep[u]+1;
            dfs(e[i].v);
            size[u]+=size[e[i].v];
        }
}
inline int lca(int x,int y){
    if(deep[x]<deep[y]) swap(x,y);
    int bin=deep[x]-deep[y];
    for(int i=19;i>=0;i--)
        if((1<<i)&bin) x=fa[x][i];
    for(int i=19;i>=0;i--)
        if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
    return x==y ? x :fa[x][0];
}

int a[N],st[N],par[N],dis[N],t[N],m,ans[N];
int remain[N];
inline bool cmp(int x,int y){return dfn[x]<dfn[y];}
inline void ins2(int x,int y){par[y]=x;dis[y]=deep[y]-deep[x];}
pii g[N];
void dp(int m){
    for(int i=m;i>1;i--){
        int x=t[i],f=par[x];
        g[f]=min(g[f],MP(g[x].fir+dis[x],g[x].sec));
    }
    for(int i=2;i<=m;i++){
        int x=t[i],f=par[x];
        g[x]=min(g[x],MP(g[f].fir+dis[x],g[f].sec));
    }
}
inline int jump1(int x,int tar){
    for(int i=19;i>=0;i--)
        if(deep[ fa[x][i] ]>=tar) x=fa[x][i];
    return x;
}
inline int jump(int x,int tar){
    int bin=deep[x]-tar;
    for(int i=19;i>=0;i--)
        if((1<<i)&bin) x=fa[x][i];
    return x;
}
int ora[N];
void solve(){
    int n=read(),m=0;
    for(int i=1;i<=n;i++)
        ora[i]=a[i]=read(),t[++m]=a[i],g[a[i]]=MP(0,a[i]);
    sort(a+1,a+1+n,cmp);

    int top=0;
    for(int i=1;i<=n;i++){
        if(!top) {st[++top]=a[i];continue;}
        int x=a[i],f=lca(x,st[top]);
        while(dfn[f]<dfn[st[top]]){
            if(dfn[f]>=dfn[st[top-1]]){
                ins2(f,st[top--]);
                if(f!=st[top]) st[++top]=f,t[++m]=f,g[f]=MP(INF,0);
                break;
            }else ins2(st[top-1],st[top]),top--;
        }
        st[++top]=x;
    }
    while(top>1) ins2(st[top-1],st[top]),top--;

    sort(t+1,t+1+m,cmp);
    dp(m);
    for(int i=1;i<=m;i++) remain[t[i]]=size[t[i]];

    ans[ g[t[1]].sec ]+=All-size[t[1]];
    for(int i=2;i<=m;i++){
        int x=t[i],f=par[x];par[x]=0;
        int t=jump(x,deep[f]+1);
        remain[f]-=size[t];
        if(g[x].sec == g[f].sec) ans[ g[x].sec ]+=size[t]-size[x];
        else{
            int len=g[x].fir + g[f].fir + dis[x], mid=deep[x]-(len/2-g[x].fir);
            if( !(len&1) && g[f].sec<g[x].sec ) mid++;
            int y=jump(x,mid);
            ans[ g[f].sec ]+=size[t]-size[y];
            ans[ g[x].sec ]+=size[y]-size[x];
        }
    }
    for(int i=1;i<=m;i++) ans[ g[t[i]].sec ]+=remain[t[i]];
    for(int i=1;i<=n;i++) printf("%d%c",ans[ora[i]],i==n?‘\n‘:‘ ‘),ans[ora[i]]=0;
}
int main(){
    //freopen("in","r",stdin);
    n=read();All=n;
    for(int i=1;i<n;i++) ins(read(),read());
    dfs(1);
    Q=read();
    while(Q--) solve();
}
时间: 2024-12-24 05:54:05

BZOJ 3572: [Hnoi2014]世界树 [虚树 DP 倍增]的相关文章

bzoj 3572 [Hnoi2014]世界树(虚树+DP)

3572: [Hnoi2014]世界树 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 645  Solved: 362[Submit][Status][Discuss] Description 世界树是一棵无比巨大的树,它伸出的枝干构成了整个世界.在这里,生存着各种各样的种族和生灵,他们共同信奉着绝对公正公平的女神艾莉森,在他们的信条里,公平是使世界树能够生生不息.持续运转的根本基石.     世界树的形态可以用一个数学模型来描述:世界树中有n个

[HNOI2014][BZOJ3572] 世界树|虚树|树上倍增LCA|树型dp|dfs序

3572: [Hnoi2014]世界树 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 555  Solved: 319[Submit][Status][Discuss] Description 世界树是一棵无比巨大的树,它伸出的枝干构成了整个世界.在这里,生存着各种各样的种族和生灵,他们共同信奉着绝对公正公平的女神艾莉森,在他们的信条里,公平是使世界树能够生生不息.持续运转的根本基石.    世界树的形态可以用一个数学模型来描述:世界树中有n个种

bzoj 3572: [Hnoi2014]世界树

再次跪虚树(DP)(两遍DP挺有意思的..) (这个题的情况,,跪) %%%http://hzwer.com/6804.html 1 #include <bits/stdc++.h> 2 #define LL long long 3 #define N 300005 4 using namespace std; 5 inline int ra() 6 { 7 int x=0,f=1; char ch=getchar(); 8 while (ch<'0' || ch>'9') {if

BZOJ 3572 世界树(虚树)

http://www.lydsy.com/JudgeOnline/problem.php?id=3572 思路:建立虚树,然后可以发现,每条边不是同归属于一端,那就是切开,一半给上面,一半给下面. 1 #include<algorithm> 2 #include<cstdio> 3 #include<cmath> 4 #include<cstring> 5 #include<iostream> 6 #define N 300005 7 int t

【BZOJ-3572】世界树 虚树 + 树形DP

3572: [Hnoi2014]世界树 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1084  Solved: 611[Submit][Status][Discuss] Description 世界树是一棵无比巨大的树,它伸出的枝干构成了整个世界.在这里,生存着各种各样的种族和生灵,他们共同信奉着绝对公正公平的女神艾莉森,在他们的信条里,公平是使世界树能够生生不息.持续运转的根本基石.世界树的形态可以用一个数学模型来描述:世界树中有n个种族,种

【BZOJ3572】【Hnoi2014】世界树 虚树

链接: #include <stdio.h> int main() { puts("转载请注明出处[辗转山河弋流歌 by 空灰冰魂]谢谢"); puts("网址:blog.csdn.net/vmurder/article/details/46506883"); } 题解: 首先构建虚树,然后在虚树上DP. 过程很简单. 先找出每个虚树节点 i 旁边最近的询问节点 neari (因为有一些lca也被加入了虚树所以虚树节点不全是询问节点,呃怕你们不懂,但其实

luogu3233 世界树 (虚树)

反正肯定要建虚树,考虑建完之后怎么做 先随便dp一下算出来距离某点最近的询问点mi[x](因为有的虚树上的点它不是询问点嘛) 那我们对于某条链x到fa[x]上的非虚树上的点(包括他们的非虚树上的孩子),要么把它分给mi[x],要么分给mi[fa[x]] 我找到这个中间点以后,在原树上倍增跳过去,算他的size 这个分法是以$\frac{len[mi[x]]+len[x]+len[mi[fa[x]]]}{2}$再加加减减一些细节决定的(len[x]表示x到fa[x]的链的长度) 除此之外,每个在虚

CodeForces - 613D:Kingdom and its Cities(虚树+DP)

Meanwhile, the kingdom of K is getting ready for the marriage of the King's daughter. However, in order not to lose face in front of the relatives, the King should first finish reforms in his kingdom. As the King can not wait for his daughter's marri

Codeforces 1111E Tree 虚树 + dp

直接把 r 加进去建虚树, 考虑虚树上的dp, 我们考虑虚树的dfs序的顺序dp过去. dp[ i ][ j ]  表示到 i 这个点为止, 分成 j 组有多少种合法方案. dp[ i ][ j ] = dp[ i - 1 ][ j ] * (j - have[ i ])  + dp[ i - 1 ][ j - 1 ], have[ i ] 表示 i 的祖先中有多少个在a中出现. #include<bits/stdc++.h> using namespace std; const int N