BZOJ 3611 [Heoi2014]:虚树+树形DP

时空隧道


今天考试T3正解是虚树…..(看到这个名字我好虚啊….)


现在我们需要处理一棵树上k个点的询问,做一遍树形DP……
复杂度是O(n)的,q个询问,感觉复杂度很爆炸…..>_<……
然后我们可以发现对于每次询问k都不是很大,有用的点貌似不超过2*k个,那么其他点都是来打酱油的?(你来打酱油还徒增复杂度??扔出去斩了QAQ)….
那么问题来了…..肿么拎出来建树??求出所有点的LCA???这就到k^2了,说不定是退化了……再想想?其实并不需要k^2个LCA,想一想就会发现,其实只有k-1个LCA(可以想一想k=3时的情况,你绝对找不出来3个LCA)……那么问题就好办了,复杂度可以降到O(2*k)了……..至于怎么建树?单调栈出场….
我们按照dfs序扫过去,维护一个dep单调不下降的栈,如果当前元素dep小于栈顶,那么就不断把栈顶与前一个元素合并(就是求LCA)……

接下来说DP:
对于求和:
f[i]代表以i为根的子树中询问点的路径总长度,siz[i]代表子树中询问点的个数
转移很简单f[root]+=f[to[i]]+siz[to[i]]*(k-siz[to[i]])*dis[root,to[i]]
(画个图YY一下就懂了)….
对于最大值:
MAX[i]代表子树中最长链(询问点到根)的长度
MAX[root]=max(MAX[root],MAX[to[i]]+dis[root,to[i]])
对于最小值:
MIN[i]代表子树中最短链(询问点到根)的长度
MIN[root]=min(MIN[root],MIN[to[i]]+dis[root,to[i]])

**然而感觉真的建出来有些浪费,在我们维护单调栈的过程中就相当于进行了一次dfs,直接DP就好….然而我只写了暴力重新建树的做法QAQing…..


代码如下:
这个代码很慢,并且有一个数据过不去(QAQ)……今天太晚了,苯宝宝想去睡觉了,明天再改……

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
//by NeighThorn 
using namespace std;
const int maxn=1000000+5;
int stk[maxn],tail,siz[maxn],M[maxn],is[maxn],k;
int n,q,hd[maxn],to[maxn*2],nxt[maxn*2],cnt,dis[maxn],fa[maxn][20+5],G[maxn];
long long sum[maxn],MAX[maxn],MIN[maxn],minans,maxans;
inline void add(int x,int y){
    to[cnt]=y;
    nxt[cnt]=hd[x];
    hd[x]=cnt++;
}
inline void dfs(int root,int f){
    M[root]=++cnt;
    for(int i=hd[root];i!=-1;i=nxt[i])
        if(to[i]!=f)
            fa[to[i]][0]=root,dis[to[i]]=dis[root]+1,dfs(to[i],root);
}
inline void init(void){
    for(int j=1;j<=20;j++)
        for(int i=1;i<=n;i++)
            fa[i][j]=fa[fa[i][j-1]][j-1];
}
inline int LCA(int x,int y){
    if(dis[x]>dis[y])
        swap(x,y);
    int d=dis[y]-dis[x];
    for(int i=20;i>=0;i--)
        if((1<<i)&d)
            y=fa[y][i];
    if(x==y)
        return x;
    for(int j=20;j>=0;j--)
        if(fa[x][j]!=fa[y][j])
            x=fa[x][j],y=fa[y][j];
    return fa[x][0];
}
inline void dfs2(int root,int f){
    siz[root]=is[root],sum[root]=0,MIN[root]=0x3f3f3f3f,MAX[root]=0;
    for(int i=hd[root];i!=-1;i=nxt[i])
        if(to[i]!=f){
            dfs2(to[i],root);
            siz[root]+=siz[to[i]];
            minans=min(minans,MIN[root]+MIN[to[i]]+dis[to[i]]-dis[root]);
            maxans=max(maxans,MAX[root]+MAX[to[i]]+dis[to[i]]-dis[root]);
            sum[root]+=sum[to[i]]+siz[to[i]]*(k-siz[to[i]])*(dis[to[i]]-dis[root]);
            MIN[root]=min(MIN[root],MIN[to[i]]+dis[to[i]]-dis[root]);
            MAX[root]=max(MAX[root],MAX[to[i]]+dis[to[i]]-dis[root]);
        }
    if(is[root])
        minans=min(minans,MIN[root]),maxans=max(maxans,MAX[root]),MIN[root]=0;
}
inline bool cmp(int x,int y){
    return M[x]<M[y];
}
signed main(void){
    freopen("project.in","r",stdin);
    freopen("project.out","w",stdout);
    cnt=0;memset(hd,-1,sizeof(hd));scanf("%d",&n);
    for(int i=1,x,y;i<n;i++)
        scanf("%d%d",&x,&y),add(x,y),add(y,x);
    dis[1]=0,fa[1][0]=1,cnt=0;dfs(1,-1);init();
    scanf("%d",&q);
    while(q--){
        scanf("%d",&k);
        memset(is,0,sizeof(is));
        for(int i=1;i<=k;i++)
            scanf("%d",&G[i]),is[G[i]]=1;
        sort(G+1,G+k+1,cmp);
        tail=0;memset(hd,-1,sizeof(hd));cnt=0;
        for(int i=1;i<=k;i++){
            while(tail>1&&dis[LCA(stk[tail],stk[tail-1])]>=dis[LCA(G[i],stk[tail])]){
                int a=stk[tail--],b=stk[tail--],c;
                c=LCA(a,b),stk[++tail]=c; 
                if(a!=c)
                    add(a,c),add(c,a)/*,cout<<a<<" "<<c<<endl*/;
                if(b!=c)
                    add(b,c),add(c,b)/*,cout<<b<<" "<<c<<endl*/;
            }
            stk[++tail]=G[i];
        }
        while(tail>1){
        /*  int a=stk[tail--],b=stk[tail--],c;
            c=LCA(a,b),stk[++tail]=c;
            if(a!=c)
                add(a,c),add(c,a);
            if(b!=c)
                add(b,c),add(c,b);
        */
            add(stk[tail],stk[tail-1]),add(stk[tail-1],stk[tail]),tail--;
        }
        minans=0x3f3f3f3f,maxans=0;dfs2(stk[1],-1);
        printf("%lld %lld %lld\n",sum[stk[1]],minans,maxans);
    }
    return 0;
}

by >_< NeighThorn

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值