bzoj 2286: [Sdoi2011]消耗战 虚树 DP

16 篇文章 0 订阅
4 篇文章 0 订阅

失踪人口回归
题目大意:一棵树,切断若干条边使得有资源的点与1(根)不联通,做m次。
题解:
裸dp挺好想,但 O(nm) 的复杂度承受不起。但是可以发现 Σki<500000 ,而且每次操作中的许多节点都是没用的,有用的只有 hi 和他们的lca。所以我们就可以通过每次把有用的点建成一棵虚树来跑,这样的话复杂度就小得多了。
关于虚树的构建,参考https://blog.sengxian.com/algorithms/virtual-tree
还有一点,假如几个资源点在同一条链上,只用管最上面的就可以了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int n,num=0,fst[250010],m,nn=0,a[250010],sta[250010],tp;
long long f[250010],inf=1LL<<50;//inf开小(1<<30)会错。。
struct edge
{
    int x,y,c,n;
}e[500010];
struct pnt
{
    int fa[20],dep,id;
    long long mn;
}p[250010];

void ins(int x,int y,int c)
{
    e[++num]={x,y,c,fst[x]};
    fst[x]=num;
}
void sd(int x,int fa)
{
    p[x].fa[0]=fa;
    p[x].dep=p[fa].dep+1;
    p[x].id=++nn;
    for(int i=1;(1<<i)<=p[x].dep;i++)
    p[x].fa[i]=p[p[x].fa[i-1]].fa[i-1];
    for(int i=fst[x];i;i=e[i].n)
    {
        int y=e[i].y;
        if(y==fa)
        continue;
        p[y].mn=min(p[x].mn,(long long)e[i].c);
        sd(y,x);
    }
}
int lca(int x,int y)
{
    if(p[x].dep<p[y].dep)
    swap(x,y);
    for(int i=19;i>=0;i--)
    if((1<<i)<=p[x].dep-p[y].dep)
    x=p[x].fa[i];
    if(x==y)
    return x;
    for(int i=19;i>=0;i--)
    if((1<<i)<=p[x].dep&&p[x].fa[i]!=p[y].fa[i])
    {
        x=p[x].fa[i];
        y=p[y].fa[i];
    }
    return p[x].fa[0];
}
int cmp(int x,int y)
{
    return p[x].id<p[y].id;
}
void dfs(int x)
{
//  printf("%d\n",x);
    f[x]=p[x].mn;
    long long s=0;
    for(int i=fst[x];i;i=e[i].n)
    {
        int y=e[i].y;
        dfs(y);
        s+=f[y];
    }
//  printf("%d %lld %lld\n",x,f[x],s);
    if(fst[x])
    f[x]=min(f[x],s);
    fst[x]=0;
}
void wk()
{
    num=0;
    f[1]=inf;
    int k,kk=0;
    scanf("%d",&k);
    for(int i=0;i<k;i++)
    {
        scanf("%d",&a[i]);
    }
    sort(a,a+k,cmp);/*
    for(int i=0;i<k;i++)
    printf("%d %d\n",a[i],p[a[i]].id);*/
    for(int i=1;i<k;i++)
    {
        int hh=lca(a[kk],a[i]);
        if(hh!=a[kk])
        a[++kk]=a[i];
    }
    sta[tp=1]=1;
    for(int i=0;i<=kk;i++)
    {
//      puts("f");
        int z=lca(sta[tp],a[i]);
        if(z==sta[tp])
        sta[++tp]=a[i];
        else
        {
//          printf("*%d %d %d\n",sta[tp],sta[tp-1],z);
            while(tp>=2&&p[sta[tp-1]].dep>=p[z].dep)
            {
//              puts("f");
                ins(sta[tp-1],sta[tp],1);
                tp--;
            }
            if(z!=sta[tp])
            {
                ins(z,sta[tp--],1);
                sta[++tp]=z;
            }
            sta[++tp]=a[i];
        }
    }
//  puts("f");
    for(int i=1;i<tp;i++)
    ins(sta[i],sta[i+1],1);
    /*
    for(int i=1;i<=num;i++)
    printf("%d %d\n",e[i].x,e[i].y);*/
    dfs(1);/*
    for(int i=0;i<=kk;i++)
    printf("%d ",f[a[i]]);
    puts("");*/
    printf("%lld\n",f[1]);
}
int main()
{
    memset(fst,0,sizeof(fst));
    memset(a,0,sizeof(a));
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int x,y,c;
        scanf("%d%d%d",&x,&y,&c);
        ins(x,y,c);
        ins(y,x,c);
    }
    p[1].mn=inf;
    p[1].dep=1;
    sd(1,0);/*
    for(int i=1;i<=n;i++)
    printf("%d ",p[i].mn);
    puts("");*/
    memset(fst,0,sizeof(fst));
    scanf("%d",&m);
    while(m--)
    {
        wk();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值