[Luogu 2495][BZOJ 2286] 消耗战

20 篇文章 0 订阅
3 篇文章 0 订阅
洛谷传送门
BZOJ传送门

题目描述

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

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

输入输出格式

输入格式:

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

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

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

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

输出格式:

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

输入输出样例

输入样例#1:

10
1 5 13
1 9 6
2 1 19
2 4 8
2 3 91
5 6 8
7 5 4
7 8 31
10 7 9
3
2 10 6
4 5 7 8 3
3 9 4 6

输出样例#1:

12
32
22

说明

【数据规模和约定】

对于10%的数据, 2n10,1m5,1kin1 2 ≤ n ≤ 10 , 1 ≤ m ≤ 5 , 1 ≤ k i ≤ n − 1

对于20%的数据, 2n100,1m100,1kimin(10,n1) 2 ≤ n ≤ 100 , 1 ≤ m ≤ 100 , 1 ≤ k i ≤ m i n ( 10 , n − 1 )

对于40%的数据, 2n1000,m>=1,ki500000,1kimin(15,n1) 2 ≤ n ≤ 1000 , m >= 1 , ∑ k i ≤ 500000 , 1 ≤ k i ≤ m i n ( 15 , n − 1 )

对于100%的数据, 2n250000,m>=1,ki500000,1kin1 2 ≤ n ≤ 250000 , m >= 1 , ∑ k i ≤ 500000 , 1 ≤ k i ≤ n − 1

解题分析

一道虚树板题, 也是蒟蒻A的第一道虚树。

考虑只有一组询问, k<n k < n ,我们可以树形dp。对于一个不需要断掉的节点 A A , 如果其子树中有需要切断的点的话,我们要么直接切断它与1号节点简单路径中花费最小的边, 要么分开断掉每个子树。我们设 sum[i] s u m [ i ] 为断掉 i i 号点及其子树的最小花费,mn[i] i i 号点到1号点简单路径上花费最小的边。 对于一个需要断掉的点j, 其 sum s u m 值初始应为 mn[j] m n [ j ] 则有如下方程:

sum[i]=min(sson[i]sum[i],mn[i]) s u m [ i ] = m i n ( ∑ s ∈ s o n [ i ] s u m [ i ] , m n [ i ] )

然后就可以一遍 O(N) O ( N ) dp得到答案。

但是如果有 m m 组询问,这样做复杂度是O(MN)的,只有40分。

然后我们就可以发现如果点数较少的话, 有效点之间的绝大多数点是没有用的,可以压进一条边。换句话说,我们只需要所有有效点和它们两两之间的 LCA L C A 就可以得到所需信息。

那么我们怎么建立这棵树?欧拉序就是我们DFS一棵树的过程,可以完全还原一棵树, 所以我们对树先做一遍DFS,求出每个点的欧拉序, 每次按欧拉序对有效点排序,模拟DFS过程压栈退栈。同时我们可以发现两点之间 LCA L C A 的总个数不会超过 ki×2 k i × 2 ,这样我们的总复杂度是小于等于 O(kilog(ki)) O ( ∑ k i l o g ( ∑ k i ) ) 的(因为小询问合在一起的总复杂度是小于一个大点)。

细节见代码。

#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cmath>
#include <cctype>
#include <limits.h>
#include <cstring>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 500050
#define ll long long
template <class T>
IN void in(T &x)
{
    x = 0; R char c = gc;
    W (!isdigit(c)) c = gc;
    W (isdigit(c))
    x = (x << 1) + (x << 3) + c - 48, c = gc;
}
int dot, q, cot, cnt, top, ecnt;
int eout[MX], head[MX], ein[MX], topf[MX], fat[MX],
sta[MX], lg[MX], dep[MX], tree[MX], siz[MX], son[MX];
ll mn[MX], sum[MX];
bool inq[MX];
IN bool cmp(const int &x, const int &y)//根据欧拉序排序
{
    int key1 = x > 0 ? ein[x] : eout[-x];
    int key2 = y > 0 ? ein[y] : eout[-y];
    return key1 < key2;
}
struct Edge
{
    int to, nex;
    ll len;
}edge[MX];
IN void addedge(const int &from, const int &to, const int &len)
{
    edge[++cnt] = {to, head[from], len};
    head[from] = cnt;
}
namespace LCA//树剖LCA, 其实也可用倍增、RMQ等LCA
{
    void DFS1(const int &now, const int &fa)
    {
        fat[now] = fa; ein[now] = ++cot; siz[now] = 1;
        for (R int i = head[now]; i; i = edge[i].nex)
        {
            if(edge[i].to == fa) continue;
            dep[edge[i].to] = dep[now] + 1;
            mn[edge[i].to] = std::min(mn[now], edge[i].len);
            DFS1(edge[i].to, now);
            siz[now] += siz[edge[i].to];
            if(siz[now] > siz[son[now]]) son[now] = edge[i].to;
        }
        eout[now] = ++cot;
    }
    void DFS2(const int &now, const int &grand)
    {
        topf[now] = grand;
        if(!son[now]) return;
        DFS2(son[now], grand);
        for (R int i = head[now]; i; i = edge[i].nex)
        {
            if(edge[i].to == fat[now] || edge[i].to == son[now]) continue;
            DFS2(edge[i].to, edge[i].to);
        }
    }
    IN int query(R int x, R int y)
    {
        W (topf[x] != topf[y])
        {
            if(dep[topf[x]] < dep[topf[y]]) std::swap(x, y);
            x = fat[topf[x]];
        }
        return dep[x] < dep[y] ? x : y;
    }
}
int main(void)
{
    int a, b, c;
    in(dot); mn[1] = INT_MAX;//注意初始化
    for (R int i = 1; i < dot; ++i)
    in(a), in(b), in(c), addedge(a, b, c), addedge(b, a, c);
    in(q); LCA::DFS1(1, 0); LCA::DFS2(1, 1);
    W (q--)
    {
        in(a); b = a;
        for (R int i = 1; i <= a; ++i)
        in(tree[i]), sum[tree[i]] = mn[tree[i]], inq[tree[i]] = true;
        std::sort(tree + 1, tree + 1 + a, cmp);
        for (R int i = 1; i < a; ++i)
        {
            c = LCA::query(tree[i], tree[i + 1]);//加入LCA
            if(!inq[c]) tree[++a] = c, inq[c] = true;
        }
        if(!inq[1]) tree[++a] = 1;//强制加入1号点
        b = a;
        for (R int i = 1; i <= b; ++i) tree[++a] = -tree[i];
        std::sort(tree + 1, tree + 1 + a, cmp);
        for (R int i = 1; i <= a; ++i)
        {
            if(tree[i] > 0) sta[++top] = tree[i];//入栈
            else
            {
                b = sta[top--]; c = sta[top];//栈中上一个是它的父节点,向上合并
                if(b != 1) sum[c] += std::min(mn[b], sum[b]);
                else printf("%lld\n", sum[1]);
                sum[b] = 0, inq[b] = false;//注意清零
            }
        }
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值