虚树dp

虚树的构建过程:

因为整个虚树只需要保留特殊点以及他们的LCA节点,所以构建过程如下:

1. 对特殊点的数组按照DFN排序(DFN是dfs到达此节点的时间戳)

2. 对每个排序后相邻的节点求他们的LCA,全部插入到特殊点数组中

3. 对插入了LCA的特殊点数组再次按照DFN排序,之后去重(同一个LCA可能重复出现多次)

4. 初始化空树边。不能用O(n)的时间进行初始化,不然复杂度会退化

5. 使用栈,利用DFN排序之后特殊点数组的欧拉序(DFN和LOW)信息进行建树(LOW是dfs离开此节点的时间戳)

6. 虚树构建完成,根节点为特殊点数组中DFN最小的元素(如果使用了标记,计算虚树的答案之后对标记进行初始化)

 

题目链接:P2495 [SDOI2011]消耗战

题目描述

在一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达。现在,我军已经侦查到敌军的总部在编号为1的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望。已知在其他k个岛屿上有丰富能源,为了防止敌军获取能源,我军的任务是炸毁一些桥梁,使得敌军不能到达任何能源丰富的岛屿。由于不同桥梁的材质和结构不同,所以炸毁不同的桥梁有不同的代价,我军希望在满足目标的同时使得总代价最小。

侦查部门还发现,敌军有一台神秘机器。即使我军切断所有能源之后,他们也可以用那台机器。机器产生的效果不仅仅会修复所有我军炸毁的桥梁,而且会重新随机资源分布(但可以保证的是,资源不会分布到1号岛屿上)。不过侦查部门还发现了这台机器只能够使用m次,所以我们只需要把每次任务完成即可。

输入输出格式

输入格式:

第一行一个整数n,代表岛屿数量。

接下来n-1行,每行三个整数u,v,w,代表u号岛屿和v号岛屿由一条代价为c的桥梁直接相连,保证1<=u,v<=n,1<=c<=100000

第n+1行,一个整数m,代表敌方机器能使用的次数。

接下来m行,每行一个整数ki,代表第i次后,有ki个岛屿资源丰富,接下来k个整数h1,h2,…hk,表示资源丰富岛屿的编号。

输出格式:

输出有m行,分别代表每次任务的最小代价。

2<=n<=250000,m>=1,sigma(ki)<=500000,1<=ki<=n-1

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 250005;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int n, m, k, h[MAXN<<1];
int fa[MAXN][20], dfn[MAXN], low[MAXN], dep[MAXN], tot, head[MAXN], cnt;
ll sum[MAXN], mi[MAXN];
struct edge{
    int v,w,next;
}e[MAXN<<1];
void add(int u,int v,int w){ e[cnt] = (edge){v,w,head[u]}, head[u] = cnt++; }
inline void dfs(int u,int f,int x,int d){
    mi[u] = x, dfn[u] = ++tot, dep[u] = d, fa[u][0] = f;
    for(int i = 1;i<20;i++) fa[u][i] = fa[fa[u][i-1]][i-1];
    for(int i = head[u];~i;i = e[i].next){
        if(e[i].v == f)continue;
        dfs(e[i].v, u, min(x,e[i].w), d+1);
    } low[u] = tot;
}
inline bool cmp(int a,int b){ return dfn[a] < dfn[b]; }
inline int lca(int u,int v){
    if(dep[u]<dep[v])swap(u,v);
    for(int d = dep[u] - dep[v], i = 0; d; d>>=1, i ++) if(d&1) u = fa[u][i];
    if(u == v)return u;
    for(int i = 19;i>=0;i--) if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
    return fa[v][0];
}
int stk[MAXN], vis[MAXN];
inline void dfs(int u){
    sum[u] = mi[u];
    if(vis[u]) return;
    sum[u] = 0;
    for(int i = head[u]; ~i; i = e[i].next){
        dfs(e[i].v);
        sum[u] += min(sum[e[i].v], 1ll*mi[e[i].v]);
    }
    sum[u] = min(1ll*mi[u], sum[u]);
}
int main(){
    scanf("%d",&n);
    for(int i = 1;i<=n;i++)head[i] = -1; cnt = 0;
    for(int i = 1,u,v,w;i<n;i++){
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w); add(v,u,w);
    }
    dfs(1,0,INF,1);  //虚树一般只需LCA的信息,dfs之后的图一般不需要保留
    scanf("%d",&m);
    mi[1] = INF;
    while(m--){
        scanf("%d",&k);
        for(int i = 1; i <= k; i ++)scanf("%d",h+i), vis[h[i]] = 1;
        sort(h+1, h+1+k, cmp);                                  //1. DFN排序
        for(int i = k; i > 1; i --) h[++k] = lca(h[i],h[i-1]);  //2.加LCA,开两倍空间
        h[++k] = 1; //有些情况必须加根节点,不加根节点的话h[1]就是根节点
        sort(h+1, h+1+k, cmp); k = unique(h+1, h+1+k) - h - 1;  //3.再次DFN排序,去重
        for(int i = 1; i <= k; i ++) head[h[i]] = -1; cnt = 0;  //4.初始化图
        for(int i = 1, top = 0; i <= k; i ++){                  //5.加边循环
            while(top && low[stk[top]] < dfn[h[i]])top--;
            if(top) add(stk[top], h[i], 0);
            stk[++top] = h[i];
        }
        dfs(1); printf("%lld\n",sum[1]);
        for(int i = 1;i<=k;i++)vis[h[i]] = 0;               //6.还原标记
    }
    return 0;
}

 

 

题目链接:CF613D Kingdom and its Cities

题意翻译

一个王国有n座城市,城市之间由n-1条道路相连,形成一个树结构,国王决定将一些城市设为重要城市。

这个国家有的时候会遭受外敌入侵,重要城市由于加强了防护,一定不会被占领。而非重要城市一旦被占领,这座城市就不能通行。

国王定了若干选择重要城市的计划,他想知道,对于每个计划,外敌至少要占领多少个非重要城市,才会导致重要城市之间两两不连通。如果外敌无论如何都不可能导致这种局面,输出-1

输入输出格式

输入格式:

第一行包含一个整数n ( 1<=n<=100000)— 表示王国城市的数量。

接下去 n-1行包含两个整数u_i,v_i ( 1<=u_{i},v_{i}<=n) ,表示第i条道路链接的两个城市编号。

下一行包含一个整数q ( 1<=q<=100000)— 表示国王的方案数。

接下去q行按一下规则给出:第一个整数k_i( 1<=k_{i}<=n) — 表示第i个方案中重要城市的个数,然后是k_i个空格隔开的整数,互不相同且范围从1到n—表示这个方案中重要城市的编号。

 \sum k_i <= 100000

输出格式:

对于每一个方案输出一个整数,表示最小的需要攻占的个数,如果试图孤立重要城市的企图都无效的话,输出-1。

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 250005;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int n,q,k;
struct Graph{;
    struct edge{
        int v,w,next;
    }e[MAXN<<1];
    int head[MAXN], cnt;
    inline void add(int u,int v){
        e[cnt].v = v, e[cnt].next = head[u], head[u] = cnt++;
    }
    inline void init(){
        memset(head,-1,sizeof head); cnt = 0;
    }
}S,T;
int h[MAXN], fa[18][MAXN], dep[MAXN], dfn[MAXN], low[MAXN], tot;
bool vis[MAXN];
void dfs(int u,int f,int d){
    fa[0][u] = f, dep[u] = d, dfn[u] = ++tot;
    for(int i = 1;i<18;i++) fa[i][u] = fa[i-1][fa[i-1][u]];
    for(int i = S.head[u];~i;i = S.e[i].next){
        if(S.e[i].v == f)continue;
        dfs(S.e[i].v,u,d+1);
    }low[u] = tot;
}
int lca(int u,int v){
    if(dep[u] < dep[v])swap(u,v);
    for(int d = dep[u] - dep[v], i = 0; d; d >>= 1, i++) if(d & 1) u = fa[i][u];
    if(u == v) return u;
    for(int i = 17;i>=0;i--)
        if(fa[i][u] != fa[i][v]) u = fa[i][u], v = fa[i][v];
    return fa[0][v];
}
inline bool cmp(int a,int b){ return dfn[a] < dfn[b]; }
int stk[MAXN], f[MAXN], g[MAXN];
void dfs(int u){
    f[u] = g[u] = 0;//f[u]表示u的子树中答案,g[u]表示u的子树中未截断的特殊点数
    for(int i = T.head[u];~i;i = T.e[i].next){
        dfs(T.e[i].v);
        f[u] += f[T.e[i].v], g[u] += g[T.e[i].v];
    }
    if(vis[u]) f[u] += g[u], g[u] = 1;
    else if(g[u] > 1) f[u] += 1, g[u] = 0;
}
int main(){
    scanf("%d",&n);
    S.init();
    for(int i = 1,u,v;i<n;i++){
        scanf("%d %d",&u,&v);
        S.add(u,v);S.add(v,u);
    }
    dfs(1,0,1);
    scanf("%d",&q);
    while(q--){
        scanf("%d", &k);
        for(int i = 1;i<=k;i++) scanf("%d",&h[i]), vis[h[i]] = 1;
        bool flag = 1;
        for(int i = 1;i<=k;i++) if(vis[fa[0][h[i]]])flag = 0;
        if(!flag){
            for(int i = 1;i<=k;i++) vis[h[i]] = 0;
            puts("-1"); continue;
        }
        sort(h+1,h+1+k,cmp);                                //1. DFN排序
        for(int i = k;i>1;i--)h[++k] = lca(h[i],h[i-1]);    //2.加LCA,开两倍空间
        sort(h+1,h+1+k,cmp); k = unique(h+1,h+1+k)-h-1;     //3.再次DFN排序,去重
        for(int i = 1;i<=k;i++)                             //4.初始化图
            T.head[h[i]] = -1, f[h[i]] = g[h[i]] = 0;
        for(int i = 1, top = 0;i<=k;i++){                   //5.加边循环
            while(top && low[stk[top]] < dfn[h[i]])top--;
            if(top)T.add(stk[top], h[i]);
            stk[++top] = h[i];
        }
        dfs(h[1]); printf("%d\n", f[h[1]]);
        for(int i = 1;i<=k;i++) vis[h[i]] = 0;              //6.还原标记
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值