首页 > 其他分享 >【XSY1976】【BZOJ2286】【SDOI2011】消耗战(虚树,dp)

【XSY1976】【BZOJ2286】【SDOI2011】消耗战(虚树,dp)

时间:2022-10-30 10:23:59浏览次数:68  
标签:insert XSY1976 BZOJ2286 top st SDOI2011 lca 节点 dp

这题的dp思想还是比较容易想的,关键是如何保证时间复杂度,这时就用到了虚树的技巧。

1.虚树是什么?(虚树的性质)

不妨设现在询问给出了\(k\)个点,我们命名这些节点为关键节点。

那么在我的建边方式中,虚树就是:

  1. 仅包括这些关键节点、它们两两之间的\(lca\)以及原树的根的一棵有向树。
  2. 但仍然保持原树的形态和祖先关系。即若关键节点\(a\)在原树中是关键节点\(b\)的祖先,则在虚树中,\(a\)仍是\(b\)的祖先。(这里的祖先包括父亲)

故只要我们按这个方法建出虚树,就会有:\(\text{虚树的点数}<=2\times k\)。不太严谨的证明:我们将其按\(dfn\)排序,那么相邻点的\(lca\)一定比较远点的\(lca\)深,也就是更接近那两个点。那么这样最多也就会新加入一个\(lca\),所以总共最多就\(2\times k\)个点。

2.怎么建虚树?

我们先将原树的\(dfn\)预处理出来,然后我们把所有的关键节点按\(dfn\)序排序。排完序后按顺序一个一个插入(接下来讲的\(insert()\)函数)。

同时我们也需要维护一个\(stack\),初始只有一个点\(root\)(\(1\)号节点),\(top=1\)。

这个栈用来维护什么呢?不妨设我们现在\(insert(u)\)。那么我们把\(insert(u)\)函数执行完后,需要保证这个栈的栈顶是\(u\),然后维护的是原树中从根到栈顶(\(u\))路径上需要加入虚树的节点,或者也可以理解为虚树中从根到栈顶(u)的路径上的点,所以若把这个栈中的点依次两两连起边来,便是一条链,但是现在还未连边。而且我们需要保证除了栈中的点之间的连边之外,已经\(insert\)的点之间的连边都已连好了。

不妨设我们现在要\(insert(u)\),那么我们先求出\(lca(u,st[top])\),然后我们设原来的栈长什么样:
\(stack=\{1,...,st[k-1],st[k],...,st[top]\}\)。

然后我们找到\(lca\)在这条链中的位置,即\(deep[st[k-1]]<deep[lca]\leqslant deep[st[k]]\),那么\(st[k]\)、\(st[k+1]\)...\(st[top]\)必然是\(lca\)的子/孙(因为\(lca\)是\(st[top]\)和\(u\)的祖先)。

那么我们考虑新建出来的栈长什么样:\(stack=\{1,...,st[k-1],lca,u\}\)。

所以我们需要\(lca\)、\(st[k]\)、\(st[k+1]\)、…、\(st[top]\)连起来并弹出栈。

然后再将\(lca\)和\(u\)入栈。

最后\(insert\)完所有点后,我们将栈中剩下的点连起来并清空栈。

为什么要按\(dfn\)排序?因为按\(dfn\)排序后,对于相邻的两个关键节点\(a\)、\(b\),\(lca(a,b)\)一定比\(a\)和其它点的\(lca\)深度深,这样能保证建的点和边不重不漏。

然后看代码:

void insert(int u)
{
    if(top==1)//特判top=1
    {
        if(st[top]!=u)st[++top]=u;//特判u=1
        return;
    }
    int lca=LCA(u,st[top]);
    for(;top>1&&dfn[st[top-1]]>=dfn[lca];top--)//建边,因为stack中存的是一条链,所以dfn[st[top-1]]>=dfn[lca]其实和deep[st[top-1]]>=deep[lca]是等价的
        add_edge(st[top-1],st[top]);
    //做完上面这段循环后,能保证dfn[st[top-1]]<dfn[lca]<=dfn[st[top]]
    if(st[top]!=lca)//判断一下,防止自环
    {
        add_edge(lca,st[top]);
        st[top]=lca;
    }
    st[++top]=u;
}

不理解?来看图模拟一下:

在这里插入图片描述

不妨设现在\(k=5\),关键节点为\(4\)、\(7\)、\(9\)、\(10\)、\(13\)(已标红),\(stack=\{1\}\)。

然后我们要先\(insert(4)\),发现\(top=1\),于是\(stack=\{1,4\}\)。

然后\(insert(7)\),发现\(lca(7,4)=1\),于是建边\((1,4)\),\(stack=\{1,7\}\)。

然后\(insert(9)\),发现\(lca(7,9)=6\),于是建边\((6,7)\),\(stack=\{1,6,9\}\)。

然后\(insert(10)\),发现\(lca(9,10)=9\),于是\(stack=\{1,6,9,10\}\)。

然后\(insert(13)\),发现\(lca(10,13)=8\),于是建边\((9,10)\)、\((8,9)\),\(stack=\{1,6,8,13\}\)。

最后将栈清空:建边\((8,13)\)、\((6,8)\)、\((1,6)\),\(stack=\{\}\)。

所以建出来的虚树长这个样:

在这里插入图片描述

现在应该理解了吧

然后我们可以做一点小优化:若关键节点\(a\)是关键节点\(b\)的祖先,那么显然只要让\(a\)和根断掉,\(b\)就绝对会和根断掉,所以我们可以忽略\(b\)。

3.怎么dp?

不妨设\(minn[u]\)为原树中从根节点到\(u\)路径上的边权最小值,\(dp[u]\)为把以\(u\)根节点的子树中的所有关键节点都与根节点切断的最小代价。

那么要么是在\(u\)的子树中就切断,或者自己切断:

\[dp[u]=min(\sum_{v=son[u]}dp[v],minn[u]) \]

那么直接树形\(dp\)就好了。

代码如下:

#include<bits/stdc++.h>
 
#define LN 18
#define N 250010
#define ll long long
#define INF 0x7fffffffffffffff
 
using namespace std;
 
struct Tree
{
    int cnt,head[N],nxt[N<<1],to[N<<1],w[N<<1];
    void init()
    {
        cnt=0;
        memset(head,0,sizeof(head));
    }
    void adde(int u,int v,int c=0)
    {
        to[++cnt]=v;
        w[cnt]=c;
        nxt[cnt]=head[u];
        head[u]=cnt;
    }
}e1,e2;
 
int n,m,idx;
int top,st[N];
int a[N];
int d[N],dfn[N],fa[N][LN];
ll minn[N],dp[N];
 
void dfs(int u)
{
    dfn[u]=++idx;
    for(int i=1;i<=17;i++)
        fa[u][i]=fa[fa[u][i-1]][i-1];
    for(int i=e1.head[u];i;i=e1.nxt[i])
    {
        int v=e1.to[i];
        if(v!=fa[u][0])
        {
            fa[v][0]=u;
            d[v]=d[u]+1;
            minn[v]=min(minn[u],(ll)e1.w[i]);
            dfs(v);
        }
    }
}
 
int LCA(int a,int b)
{
    if(d[a]<d[b])swap(a,b);
    for(int i=17;i>=0;i--)
        if(d[fa[a][i]]>=d[b])
            a=fa[a][i];
    if(a==b)
        return a;
    for(int i=17;i>=0;i--)
        if(fa[a][i]!=fa[b][i])
            a=fa[a][i],b=fa[b][i];
    return fa[a][0];
}
 
bool cmp(int a,int b)
{
    return dfn[a]<dfn[b];
}
 
void insert(int u)
{
    if(top==1)
    {
        if(u!=1)st[++top]=u;
        return;
    }
    int lca=LCA(u,st[top]);
    if(lca==st[top])
        return;
    for(;top>1&&dfn[st[top-1]]>=dfn[lca];top--)
        e2.adde(st[top-1],st[top]);
    if(st[top]!=lca)
    {
        e2.adde(lca,st[top]);
        st[top]=lca;
    }
    st[++top]=u;
}
 
void dfs2(int u)
{
    if(!e2.head[u])
    {
        dp[u]=minn[u];
        return;
    }
    dp[u]=0ll;
    for(int i=e2.head[u];i;i=e2.nxt[i])
    {
        int v=e2.to[i];
        dfs2(v);
        dp[u]+=dp[v];
    }
    dp[u]=min(dp[u],minn[u]);
    e2.head[u]=0;
}
 
int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        e1.adde(u,v,w),e1.adde(v,u,w);
    }
    d[1]=1,minn[1]=INF;
    dfs(1);
    scanf("%d",&m);
    while(m--)
    {
        int k;
        scanf("%d",&k);
        for(int i=1;i<=k;i++)
            scanf("%d",&a[i]);
        sort(a+1,a+k+1,cmp);
        e2.cnt=0;
        st[top=1]=1;
        for(int i=1;i<=k;i++)
            insert(a[i]);
        for(;top>1;top--)
            e2.adde(st[top-1],st[top]);
        dfs2(1);
        printf("%lld\n",dp[1]);
    }
    return 0;
}

标签:insert,XSY1976,BZOJ2286,top,st,SDOI2011,lca,节点,dp
From: https://www.cnblogs.com/ez-lcw/p/16840590.html

相关文章

  • P2486 [SDOI2011]染色
    #include<iostream>#include<vector>usingnamespacestd;#defineintlonglongconstintN=1e5+1;intn,m;vector<int>to[N];intdep[N],fa......
  • P2491 [SDOI2011] 消防
    P2491SDOI2011消防算法竞赛进阶指南P374解法3(解法2为P1099树网的核),7FA4.3.2.5.3,LuoguP2491SDOI2011二分答案mid在树的直径上找离两端最远且距离小于mid......