欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

[HEOI2014] 大工程

程序员文章站 2022-06-22 12:47:35
「题意」给你一棵树,每次询问若在在选中的k个点两两连接无相边,边权为原来树上的点对距离,求这些边的:1)权值和 2)最短的边 3)最长的边。所有k之和$\le$2 n。 「分析」虚树模板题。(但是独立写出来还是很振奋人心的合)直接考虑对虚树dp,设pmn[x]为x到x的子树内的关键点的最短距离,pm ......

「题意」给你一棵树,每次询问若在在选中的k个点两两连接无相边,边权为原来树上的点对距离,求这些边的:1)权值和 2)最短的边 3)最长的边。所有k之和$\le$2*n。
「分析」虚树模板题。(但是独立写出来还是很振奋人心的合)直接考虑对虚树dp,设pmn[x]为x到x的子树内的关键点的最短距离,pmx[x]为最长距离,sum[x]为x到子树内所有关键点的距离之和。这些都很好处理。统计答案利用树形dp的常用技巧——有当前子树和以前的子树进行组合。

「实现」

/*
    写对啦!woc
*/

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int n=1e6+10;
const int inf=0x3f3f3f3f;

int n,q,k;
int dfn[n],dep[n],fa[n][20];
vector<int> e[n];

void pre(int x,int pa) {
    static int cnt=0;
    dfn[x]=++cnt;
    dep[x]=dep[fa[x][0]=pa]+1;
    for(int i=1; (1<<i)<=dep[x]; ++i) 
        fa[x][i]=fa[fa[x][i-1]][i-1];
    for(unsigned i=0; i<e[x].size(); ++i)
        if(e[x][i]!=pa) pre(e[x][i],x);
}
int lca(int x,int y) {
    if(dep[x]<dep[y]) swap(x,y);
    int dif=dep[x]-dep[y];
    for(int i=19; ~i; --i) 
        if(dif&(1<<i)) x=fa[x][i];
    if(x==y) return x;
    for(int i=19; ~i; --i) 
        if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}

int a[n],s[n],top,cnt;
int head[n],to[n<<1],len[n<<1],last[n<<1];
void add_edge(int x,int y,int w) {
    to[++cnt]=y;
    len[cnt]=w;
    last[cnt]=head[x];
    head[x]=cnt;
}
bool cmp(int x,int y) {
    return dfn[x]<dfn[y];
}
void ist(int x) {
    if(top==1) {
        if(x!=1) s[++top]=x; 
        return;
    }
    int t=lca(s[top],x);
    if(t!=s[top]) {
        for(; top>1&&dfn[s[top-1]]>=dfn[t]; top--) 
            add_edge(s[top-1],s[top],dep[s[top]]-dep[s[top-1]]);
        if(t!=s[top]) add_edge(t,s[top],dep[s[top]]-dep[t]), s[top]=t;
    }
    s[++top]=x;
}

bool mark[n];
ll lmn[n],lmx[n],siz[n],sum[n];
ll pum,pmn,pmx;

void dfs(int x) {
    lmn[x]=inf;
    lmx[x]=-inf;
    sum[x]=siz[x]=0;
    if(mark[x]) {
        lmx[x]=lmn[x]=0;
        siz[x]=1;
    }
    for(int i=head[x]; i; i=last[i]) {
        dfs(to[i]);
        pmn=min(pmn,lmn[x]+lmn[to[i]]+len[i]);
        pmx=max(pmx,lmx[x]+lmx[to[i]]+len[i]);
        pum+=sum[x]*siz[to[i]]
            +(siz[x]-mark[x])*sum[to[i]]
            +(siz[x]-mark[x])*len[i]*siz[to[i]];
        lmn[x]=min(lmn[x],lmn[to[i]]+len[i]);
        lmx[x]=max(lmx[x],lmx[to[i]]+len[i]);
        sum[x]+=sum[to[i]]+siz[to[i]]*len[i];
        siz[x]+=siz[to[i]];
    }
    if(mark[x]) pum+=sum[x];
    head[x]=0;
    mark[x]=0;
}

void print() {
printf("asphaush tree: \n");
    static queue<int> q;
    q.push(1);
    while(!q.empty()) {
        int x=q.front(); q.pop();
        for(int i=head[x]; i; i=last[i]) {
            printf("%d -> %d, length is %d\n",x,to[i],len[i]);
            q.push(to[i]);
        }
    }
}

void solve() {
//printf("\nnew solving case: \n");
    scanf("%d",&k);
    for(int i=1; i<=k; ++i) {
        scanf("%d",&a[i]);
        mark[a[i]]=true;
    }
    sort(a+1,a+k+1,cmp);
    cnt=0;
    s[top=1]=1;
    for(int i=1; i<=k; ++i) ist(a[i]);
    for(; top>1; top--)
        add_edge(s[top-1],s[top],dep[s[top]]-dep[s[top-1]]);
//    print();
    pum=0;
    pmn=inf;
    pmx=-inf;
    dfs(1);
    printf("%lld %lld %lld\n",pum,pmn,pmx);
}

int main() {
    scanf("%d",&n);
    for(int x,y,i=n; --i; ) {
        scanf("%d%d",&x,&y);
        e[x].push_back(y);
        e[y].push_back(x);
    }
    pre(1,0);
    scanf("%d",&q);
    while(q--) solve();
    return 0;
}

/*
10 
2 1 
3 2 
4 1 
5 2 
6 4 
7 5
8 6 
9 7 
10 9 

5 

2 
5 4 

2 
10 4 

2 
5 2 

2 
6 1 

2 
6 1
*/