联盟 (Alliances)

本文介绍了一种解决树状结构中帮派控制区域及首都选取问题的方法。通过预处理帮派控制区域的最高点,利用LCA算法快速计算出在不同询问下首都到帮派控制区最近城市的距离。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

暂无链接

题目描述

  树国是一个有 n 个城市的国家,城市编号为 1∼n。连接这些城市的道路网络形如一棵树,即任意两个城市之间有恰好一条路径。城市中有 k 个帮派,编号为 1∼k。每个帮派会占据一些城市,以进行非法交易。有时帮派之间会结盟,这就使得城市更加不安全了。同一座城市中可能有多个帮派。

  当一些帮派结成联盟时,他们会更加强大,同时也更加危险。他们所控制的城市数会显著增加。具体地,一个联盟控制的城市是联盟中所有帮派所占据的城市,再加上这些城市两两之间路径上的所有城市。

  shy 是树国的市长,他想要选择一个城市作为首都。在决定之前,他要先做一些调研。 为此,他找来你帮他回答一些询问,你能做到吗?在每个询问中,shy 会选择一个城市作为首都,同时会告诉你当前活跃的帮派的集合。在这个询问中,你只需要考虑给定的集合中的帮派,其他的帮派你可以当作不存在。已知给定集合中的这些帮派结成了联盟,shy 希望抓获联盟中的人,以得到关于整个联盟的一些信息。为此,他要找到被联盟控制的所有城市中离首都最近的一座城市到首都的距离。有可能首都本身就被控制了,此时答案为 0。请注意,询问之间相互独立,互不影响。

输入格式

  输入的第一行包含一个整数 n,代表树国中的城市数。

  接下来 n−1 行,每行包含两个整数 u 和 v,代表城市 u和 v 之间存在一条道路。

  接下来一行包含一个整数 k,代表树国中的帮派数。

  接下来 k 行,每行描述一个帮派。第 i 行的第一个整数 c[i] 代表第 i 个帮派占据的城市数,接下来 c[i]个整数,代表被第 i 个帮派占据的城市。

  接下来一行包含一个整数 Q,代表询问数。

  接下来 Q 行,每行描述一个询问。每行的前两个整数 V 和 t[i]代表本次询问中的首都与需要考虑的帮派集合的大小。接下来t[i]个整数代表本次询问中需要考虑的帮派。

输出格式

  对于每个询问,输出一行,包含一个整数,代表询问的答案。

输入样例

7
1 2
1 3
2 4
2 5
3 6
3 7
2
2 6 7
1 4
3
5 1 2
1 1 1
5 2 1 2

输出样例

2
1
1

数据规模

对于 30%的数据, 1n,k 1 ≤ n , k , Q1000 Q ≤ 1000 , 1≤每个帮派占据城市数之和≤1000, 1≤每个询问中考虑的帮派数之和≤1000;

对于 60%的数据, 1n,k 1 ≤ n , k , Q100000 Q ≤ 100000 , 1≤每个帮派占据城市数之和≤100000, 1≤每个询问中考虑的帮派数之和≤100000;

对于 100%的数据, 1n,k 1 ≤ n , k , Q500000 Q ≤ 500000 , 1≤每个帮派占据城市数之和≤500000, 1≤每个询问中考虑的帮派数之和≤500000。

解题分析

首先, 这道题十分考语文功底:每个帮派占据城市数之和小于500000不是指一个帮派会占领500000个城市,而是指所有帮派占领的城市总数小于500000, 否则显然读入数据都要读到明年去了…

首先, 我们发现当询问时帮派会合并并且所有将要合并的帮派中所有的城市之间的路径都会相连,即使只有一个帮派被考虑也是如此,所以我们可以预先将一个帮派中的所有的城市求出LCA, 表示控制区域最高的地方在哪里。如图:

LCA(B1,B2) L C A ( B 1 , B 2 ) 并未画出, 所有点下方的数字表示dfs序。)

当A、B两个帮派合并时, 他们的控制范围的最高点就会扩展至root(如上图)。

现在我们只考虑A帮派,假设首都在dfs序为13的点, 那么如何确定首都是否被控制呢?显然我们将首都与最高点做一遍LCA, 发现结果并不是A帮派的最高点, 所以我们可以确定首都在另一棵子树上, 与最近的被占领的城市距离为

(dep(capital)dep(root))+(dep(LCAA1,A2)dep(root)) ( d e p ( c a p i t a l ) − d e p ( r o o t ) ) + ( d e p ( L C A A 1 , A 2 ) − d e p ( r o o t ) )

如果我们考虑A、B帮派, 并且首都在dfs序为7的点, 我们发现如果像上面那样判断会得到首都和帮派控制范围在同一子树上。

对于这种情况, 显然若首都被控制, 那么它肯定在某个原先已经被控制的点的上部。那么我们可以取dfs序在首都两侧的两个原来已被帮派控制的城市(因为我们并不能确定这首都是位于这两个点的左上侧还是右上侧)与首都做LCA, 若结果为首都则说明首都就在帮派控制的链上, 否则说明首都要么在帮派控制的范围之下, 要么像上面样例一样挂在一个侧链上。此时距离即为

dep(capital)dep(LCAcapital,city) d e p ( c a p i t a l ) − d e p ( L C A c a p i t a l , c i t y )

虽然复杂度有点没对(每次要枚举所有被帮派控制的城市),但是居然rk1过了

代码如下:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <vector>
#define R register
#define gc getchar()
#define W while
#define IN inline
#define MX 1000005
template <typename A>
IN void in (A &x)
{
    x = 0; R char c = gc;
    W (!isdigit(c)) c = gc;
    W (isdigit(c))
    {x = (x << 1) + (x << 3), x += c - 48, c = gc;}
}
namespace LCA
{
    //不使用Tarjan LCA, 离线太麻烦
    int topf[MX], fat[MX], son[MX], tot[MX], dep[MX], head[MX], dfs[MX];
    struct Edge
    {
        int to, nex;
    }edge[MX << 1];
    int cnt, n;
    IN void addedge(const int &from, const int &to)
    {
        edge[++cnt] = (Edge){to, head[from]};
        head[from] = cnt;
    }
    int dfs1(int now, int deep, int fa)
    {
        dfs[now] = ++n;
        dep[now] = deep;
        tot[now] = 1;
        fat[now] = fa;
        int ms = -1;
        for (R int i = head[now]; i; i = edge[i].nex)
        {
            if(edge[i].to == fat[now]) continue;
            tot[now] += dfs1(edge[i].to, deep + 1, now);
            if(tot[edge[i].to] > ms) ms = tot[edge[i].to], son[now] = edge[i].to;
        }
        return tot[now];
    }
    void dfs2(int now, int gra)
    {
        topf[now] = gra;
        if(!son[now]) return;
        dfs2(son[now], gra);
        for (R int i = head[now]; i; i = edge[i].nex)
        {
            if(edge[i].to == son[now] || edge[i].to == fat[now]) continue;
            dfs2(edge[i].to, edge[i].to);
        }
    }
    IN int query(int x, int y)
    {
        W (topf[x] != topf[y])
        {
            if(dep[topf[x]] > dep[topf[y]]) x = fat[topf[x]];
            else y = fat[topf[y]];
        }
        if(dep[x] > dep[y]) return y;
        return x;
    }
}
int gang[MX], num[MX], inc[MX];
int dot, q, group, capi, cons;
std::vector <int> dom[MX];//控制的城市
using std::min;
using namespace LCA;
int main()
{
    int a, b;
    in(dot);
    for (R int i = 1 ;i < dot; ++i)
    {
        in(a), in(b);
        addedge(a, b);
        addedge(b, a);
    }
    dfs1(1, 1, 0);
    dfs2(1, 1);
    in(group);
    for (R int i = 1; i <= group; ++i)
    {
        in(num[i]);
        for (R int j = 1; j <= num[i]; ++j)
        in(b), dom[i].push_back(b);
    }
    for (R int i = 1; i <= group; ++i)
    {
        if(num[i] == 1) gang[i] = dom[i][0];
        else
        {
            a = dom[i][0];
            for (R int j = 1; j < num[i]; ++j)
            a = query(a, dom[i][j]);
            gang[i] = a;//帮派控制的最高点
        }
    }
    in(q);
    W (q--)
    {
        in(capi);
        in(cons);
        for (R int i = 1; i <= cons; ++i)
        {in(inc[i]);}
        a = gang[inc[1]];
        for (R int i = 2; i <= cons; ++i)
        {a = query(gang[inc[i]], a);}//合并后的最高点
        b = query(capi, a);
        if(b != a)//不在控制的子树下
        {
            printf("%d\n", dep[a] - dep[b] + dep[capi] - dep[b]);
            continue;
        }
        else
        {
            int pre = 0, suc = 750000;
            dfs[pre] = 0, dfs[suc] = 9999999;
            for (R int i= 1; i <= cons; ++i)
            {
                for (R int j = dom[inc[i]].size() - 1; j >= 0; --j)
                {
            //复杂度不对的找前驱后继
            //可以先将dom sort了, 再log(N)查找
                    if(dfs[dom[inc[i]][j]] >= dfs[pre] && dfs[dom[inc[i]][j]] <= dfs[capi]) 
                    pre = dom[inc[i]][j];
                    if(dfs[dom[inc[i]][j]] <= dfs[suc] && dfs[dom[inc[i]][j]] >= dfs[capi]) 
                    suc = dom[inc[i]][j];
                }
            }
            if(!pre)//两种特殊情况:没有前驱, 没有后继, 需要特判
            {
                a = query(suc, capi);
                printf("%d\n", dep[capi] - dep[a]);
                continue;
            }
            else if(suc == 750000)
            {
                a = query(pre, capi);
                printf("%d\n", dep[capi] - dep[a]);
                continue;
            }
            else
            {
                a = query(suc, capi);
                b = query(pre, capi);
                printf("%d\n", min(dep[capi] - dep[a], dep[capi] - dep[b]));
            }
        }
    }
    return 0;
}

PS:下面为一组良心较大样例

(Author: Rockdu ; Name : Palace Gadgetzan)

47
1 2
2 4
4 8
8 9
9 11
9 12
12 18
18 20
18 21
12 19
19 22
19 23
19 24
8 10
10 14
10 13
13 15
15 25
25 40
40 42
40 41
41 46
15 26
2 5
5 16
5 17
1 3
3 7
3 6
6 32
6 27
27 30
30 31
31 33
33 39
33 43
43 44
44 45
31 35
30 34
34 36
27 28
28 29
29 47
47 37
37 38
3
6 9 12 10 14 24 42
9 20 18 21 40 41 46 35 45 44
7 22 23 5 7 6 34 47
8
38 1 2
7 1 2
26 2 1 2
46 1 3
24 2 2 3
39 3 1 2 3
1 2 2 3
21 2 1 3

answer:

5
1
1
7
1
1
0
2
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值