洛谷 P2892 [NOI2007] 追捕盗贼

题目背景

spj来源:loj-Robin。已获得授权。

https://www.luogu.org/paste/dxytr6gc

附spj,一些修改部分未按照代码规范,请各位谅解。

题目描述

魔法国度 Magic Land 里最近出现了一个大盗 Frank,他在 Magic Land 四处作案,专门窃取政府机关的机密文件(因而有人怀疑 Frank 是敌国派来的间谍)。

为了捉住 Frank,Magic Land 的安全局重拳出击!

Magic Land 由 N 个城市组成,并且这 N 个城市又由恰好 N-1 条公路彼此连接起来,使得任意两个城市间都可以通过若干条公路互达。从数据结构的角度我们也可以说,这 N 个城市和 N-1 条公路形成了一棵树。

例如,下图就是 Magic Land 的一个可能格局(4 个城市用数字编号,3 条公路用字母编号):

大盗 Frank 能够在公路上以任意速度移动。

比方说,对于上图给出的格局,在 0.00001 秒钟内(或者任意短的一段时间内),Frank 就可以从城市 1 经过城市 2 到达城市 4,中间经过了两条公路。

想要生擒 Frank 困难重重,所以安全局派出了经验丰富的警探,这些警探具有非凡的追捕才能:

  1. 只要有警探和 Frank 同处一个城市,那么就能够立刻察觉到Frank,并且将其逮捕。

  2. 虽然 Frank 可以在公路上以任意快的速度移动,但是如果有警探和 Frank 在同一条公路上相遇,那么警探也可以立刻察觉到 Frank 并将其逮捕。

安全局完全不知道 Frank 躲在哪个城市,或者正在哪条公路上移动,所以需要制定一个周密的抓捕计划,计划由若干 步骤组成。在每一步中,可以做如下几件事中的一个:

  1. 在某个城市空降一位警探。警探可以直接从指挥部空降到 Magic Land 的任意一个城市里。此操作记为“L x”,表示在编号为 x 的城市里空降一位警探。耗时 1 秒。

  2. 把留在某个城市里的一位警探直接召回指挥部。以备在以后的步骤中再度空降到某个城市里。此操作记为“B x”。表示把编号为 x 的城市里的一位警探召回指挥部。耗时 1 秒。

  3. 让待在城市 x 的一位警探沿着公路移动到城市 y,此操作记为“M x y”。耗时 1 秒。当然,前提是城市 x 和城市 y 之间有公路。如果在警探移动的过程中,大盗 Frank 也在同一条公路上,那么警探就抓捕到了Frank。

现在,由你来制定一套追捕计划,也就是给出若干个步骤,需要保证:无论大盗 Frank 一开始躲在哪儿,也无论 Frank 在整个过程中如何狡猾地移动(Frank大盗可能会窃取到追捕行动的计划书,所以他一定会想尽办法逃避),他一定会被缉拿归案。

希望参与的警探越少越好,因为经验丰富的警探毕竟不多。

例如对于前面所给的那个图示格局,一个可行的计划如下:

  1. L 2 在城市 2 空降一位警探。注意这一步完成之后,城市 2 里不会有 Frank,否则他将被捉住。

  2. L 2 再在城市 2 空降一位警探。

  3. M 2 1 让城市 2 的一位警探移动到城市 1。注意城市 2 里还留有另一位警探。这一步完成之后,城市 1 里不会有 Frank,公路 A 上也不会有 Frank。也就是说,假如 Frank 还没有被逮捕,那么他只能是在城市 3 或城市 4 里,或者公路 B 或公路 C 上。

  4. B 1 召回城市 1 的一位警探。注意虽然召回了这位警探,但是由于我们始终留了一位警探在城市 2 把守,所以 Frank 仍然不可能跑到城市 1 或者是公路 A 上。

  5. L 3 在城市 3 空降一位警探。注意这一步可以空降在此之前被召回的那位警探。这一步完成之后,城市 3 里不会有 Frank,否则他会被捉住。

  6. M 3 2 让城市 3 里的一位警探移动到城市 2。这一步完成之后,如果 Frank 还没有被捉住,那他只能是在公路 C 上或者城市 4 里。注意这一步之后,城市 2 里有两位警探。

  7. M 2 4 让城市 2 里的一位警探移动到城市 4。这一步完成之后,Frank 一定会被捉住,除非他根本就没来 Magic Land。

这个计划总共需要 2 位警探的参与。可以证明:如果自始至终只有 1 名或者更少的警探参与,则 Frank 就会逍遥法外。

你的任务很简单:对于一个输入的 Magic Land 的格局,计算 S,也就是为了追捕 Frank 至少需要投入多少位警探,并且给出相应的追捕计划步骤。

输入格式

输入文件给出了 Magic Land 的格局。

第一行一个整数 N,代表有 N 个城市,城市的编号是 1~N。

接下来 N-1 行,每行有两个用空格分开的整数 x i ,y i ,代表城市 x i ,y i之间有公路相连。保证 1≤x i ,y i ≤N

输出格式

向输出文件输出你所给出的追捕计划。

第一行请输出一个整数 S,代表追捕计划需要多少位警探。

第二行请输出一个整数 T,代表追捕计划总共有多少步。

接下来请输出 T 行,依次描述了追捕计划的每一步。每行必须是以下三种形式之一:

”L x”,其中 L 是大写字母,接着是一个空格,再接着是整数 x,代表在城市 x 空降一位警探。你必须保证 1≤x≤N。

“B x”,其中 B 是大写字母,接着是一个空格,再接着是整数 x,代表召回城市 x 的一位警探。你必须保证 1≤x≤N,且你的计划执行到这一步之前,城市 x 里面确实至少有一位警探。

“M x y”,其中 M 是大写字母,接着是一个空格,再接着是整数 x,再跟一个空格,最后一个是整数 y。代表让城市 x 的一位警探沿着公路移动到城市 y。你必须保证 1≤x, y≤N,且你的计划执行到这一步之前,城市 x 里面确实至少有一位警探,且城市 x, y 之前确实有公路。

必须保证输出的 S 确实等于追捕计划中所需要的警探数目。

输入输出样例

输入 #1复制

4
1 2
3 2
2 4

输出 #1复制

2
7
L 2
L 2
M 2 1
B 1
L 3
M 3 2
M 2 4

说明/提示

对于任何一个测试点:

如果输出的追捕计划不合法,或者整个追捕计划的步骤数 T 超过了 20000,或者追捕计划结束之后,不能保证捉住 Frank,则不能得分。

否则,用你输出的 S 和我们已知的标准答案 S * 相比较:
1. 若 S<S * ,则得到 120%的分。
2. 若 S=S * ,则得到 100%的分。
3. 若 S * <S≤S * +2,则得到 60%的分。
4. 若 S * +2<S≤S * +4,则得到 40%的分。
5. 若 S * +4<S≤S * +8,则得到 20%的分。
6. 若 S>S * +8,则得到 10%的分。

输入保证描述了一棵连通的 N 结点树,1≤N≤1 000。

下面设 ��→�Tu→v​ 表示树 �T 以 �u 为根时 �v 的子树内所有点的生成子树,�(�)S(T) 表示树 �T 的答案,一条路径的“旁枝”为把路径上的点和边去掉后留下的子树的集合(这里和路径上的点相连的边仍然保留)。

一定存在一个最优方案,这其中能找出一个“警长”,它走过的是一条简单路径,称为“大道”,设“大道”内“警长”走到的第 �i 个节点为 ��vi​,且“警长”在 ��vi​ 时其他警察会把和 ��vi​ 相连的所有旁枝查完,答案即为所有“大道”的所有“旁枝”的答案最大值加一。

有一个比较显然的性质是 �⊆�A⊆B 时 �(�)≤�(�)S(A)≤S(B)。

还有一个重要性质是若存在一个度数大于等于三的点 �u 和三条边 (�,�),(�,�),(�,�)(u,a),(u,b),(u,c),则 �(�)>min⁡(�(��→�),�(��→�),�(��→�))S(T)>min(S(Tu→a​),S(Tu→b​),S(Tu→c​)),如果因为假设 �(��→�)=�(��→�)=�(��→�)=�(�)S(Tu→a​)=S(Tu→b​)=S(Tu→c​)=S(T),且先全部清查 ��→�Tu→a​ 后再查 ��→�Tu→b​ 最后再查 ��→�Tu→c​,那么必然有一个时刻要把全部警力放到 ��→�Tu→b​ 里,那么此时 �u 无人值守,��→�Tu→c​ 又没查完,��→�Tu→a​ 就白查了。

如果把“大道”设为 �T 的重心,那么可以得到一个 log⁡∣�∣log∣T∣ 级别的构造方案,所以 �(�)S(T) 最多是 log⁡∣�∣log∣T∣ 级别的。

考虑找到这么样的一个“大道”使得答案最优,仍然设第 �i 个节点为 ��vi​,点数为 �l,那么不难得到下面的充要条件:

∀1≤�≤�,(��,�)∈�,�≠��−1,�≠��+1,�(���→�)<�(�)∀1≤i≤l,(vi​,s)∈T,s=vi−1​,s=vi+1​,S(Tvi​→s​)<S(T)

称使得 ∀(�,�)∈�,�(��→�)<�(�)∀(u,v)∈T,S(Tu→v​)<S(T) 的 �v 为 �T 的 Hub,此时 Hub 单独构成一个“大道”。

设一个边集 �={(�,�)∣(�,�)∈�,�(��→�)=�(��→�)=�(�)}X={(u,v)∣(u,v)∈T,S(Tu→v​)=S(Tv→u​)=S(T)},当 �T 内没有 Hub 时 �X 必然非空,因为对于一个非 Hub 点 �v 设 �(�)={(�,�)∣(�,�)∈�,�(��→�)=�(�)}f(v)={(u,v)∣(u,v)∈T,S(Tv→u​)=S(T)},那么没有 Hub 时根据抽屉原理必然有至少一组 �(�)=�(�)f(u)=f(v),那么 (�,�)∈�(u,v)∈X。

且因为上面的重要性质不会有三条 �X 边连在一个点上。

且 �X 联通,否则存在一条 (�,�)∉�(x,y)∈/X 使得把 (�,�)(x,y) 两边均有 �X 内的边,那么必然存在 ��→�⊆��→�Ta→b​⊆Tx→y​ 且 (�,�)∈�(a,b)∈X,那么 �(��→�)≤�(��→�)≤�(�)S(Ta→b​)≤S(Tx→y​)≤S(T),则 �(��→�)=�(��→�)=�(�)S(Ta→b​)=S(Tx→y​)=S(T),同理 �(��→�)=�(�)S(Ty→x​)=S(T),得出矛盾。

所以 �X 里的边构成了一条路径,会发现这条路径满足了上面的充要条件,构成了一条“大道”。

好像这个结论就是一个对这个做法第一步的证明?


对于一棵树 �T 和一个节点 �r,考虑把节点分成下面四种类型:

  • H:�r 是 �T 的 Hub

  • E:�r 是 �T 的“大道”的端点或者“大道”的端点对应的旁枝中的点。

  • I:�r 是 �T 的“大道”中的点但不是端点。

  • M:其他情况。

注意 Hub 也算“大道”的端点。

主要思想随便选一个根,然后树形 dpdp 完子树后要支持把一条边挂到根上去、根从儿子换到父亲的换根操作和两个子树信息的合并操作,维护的信息要记录答案,“大道”端点,根的类型,以及根如果是 M 型的话根所在旁枝的递归信息,如果不是 H 那么这里的“大道”是上面 �X 构成的那条,“把一条边挂上去”这个操作可以视为合并操作完成。

换根时前两个信息不变,此时新根度数为一,分为这么一些情况:

  • 若原根为 E 或 H 则新根必然为 E

  • 若原根为 I,答案为一时这棵子树就是条链,新根为 E,否则新根可以直接设为 M,递归信息要处理的子树内只有一条边容易直接计算。

  • 若原根为 M,那么新根也为 M,递归信息需要递归调用换根来完成,由于递归信息内答案严格递减所以递归次数的级别最多为 �(log⁡�)O(logn)。

合并时根在两边的度数可能不止为一了,可以分为两边答案相不相等来讨论:

两边答案相等时:

  • 若两边均为 H 则根为 H 答案不变。

  • 若一边为 H 一边为 I 则根为 I 答案不变。

  • 若一边为 H 一边为 E 则根为 E 答案不变,“大道”的端点可以设为根和 E 中的那条“大道”中离根更远的那个端点,因为“警长”多走不会使这边的答案更劣,且走到根后就能把另一边给扫了,而走没走到根的话是会导致答案变大的。

  • 若两边均为 E 则根为 I 答案不变,“大道”的端点可以设为两边的的“大道”中离根更远的那个端点,理由一样。

  • 若一边为 M 则根为 H 答案加一,因为设 M 那边为 �1T1​ 另一边为 �2T2​ 合并后为 �T,��vi​ 在 �1T1​ 的“大道”上且根在 ��→�Ta→b​ 内,那么 �(��→�)≥�(�2)=�(�1)S(Ta→b​)≥S(T2​)=S(T1​),�(���→��−1)=�(���→��+1)=�(�1)S(Tvi​→vi−1​​)=S(Tvi​→vi+1​​)=S(T1​),根据上面的重要性质所以 �(�)>�(�1)S(T)>S(T1​),此时根设为 Hub 就能取到 �(�)=�(�1)+1S(T)=S(T1​)+1 的下界。

  • 其他情况(其实只有两边均为 I 和一边 I 一边 E)和上一种一样。

两边答案不相等时,不妨设答案大的那边为 �1T1​ 小的那边为 �2T2​:

  • 若 �1T1​ 中根为 H 或 I 则答案为 �(�1)S(T1​),“大道”是 �1T1​ 的“大道”,根自然也是 �1T1​ 中的类型。

  • 若 �1T1​ 中根为 E 则情况类似两边相等时的第三种讨论,根为 E 答案为 �(�1)S(T1​),“大道”的端点可以设为根和 �1T1​ 中的那条“大道”中离根更远的那个端点。

  • 若 �1T1​ 中根为 M,设根所在 �1T1​ 的大道旁枝和 �2T2​ 的并为 �3T3​,则又要根据记录的递归信息来递归计算 �3T3​ 的信息,和上面一样递归次数的级别最多为 �(log⁡�)O(logn),然后要合并 �1T1​ 和 �3T3​ 又要讨论:

  • 若 �(�1)>�(�3)S(T1​)>S(T3​),则根为 M 答案为 �(�1)S(T1​),“大道”是 �1T1​ 的“大道”,递归信息为 �3T3​ 的信息。

  • 若 �(�1)=�(�3)S(T1​)=S(T3​),则情况类似两边相等时的第五种讨论,根为 H 答案加一。

  • �(�1)<�(�3)S(T1​)<S(T3​) 是不可能的,因为把根设为“大道”后 �(�3)S(T3​) 的答案为“根所在 �1T1​ 的大道旁枝”和 �2T2​ 这两个部分的较大值加一,这是不会大于 �(�1)S(T1​) 的

至此我们终于完成了这个分讨。


求出答案后要构造方案,这个就直接算完求出“大道”以后递归求旁枝方案就行了,单次求答案是单 log 的,由于递归层数是 log 的所以时间复杂度双 log,比较难卡满。

因为同一条边不会走两次,所以构造方案的步数最多为 3�3n

代码如下:

#include<bits/stdc++.h>

#define ll long long
#define INF 2147483647

inline int inp(){
    char c = getchar();
    while(c < '0' || c > '9')
        c = getchar();
    int sum = 0;
    while(c >= '0' && c <= '9'){
        sum = sum * 10 + c - '0';
        c = getchar();
    }
    return sum;
}

int head[100010];
int nxt[20010];
int end[20010];
char type[100000];
int num1[100000], num2[100000];
int cnt = 0;
int cou = 0;
int f[20010];

void link(int a, int b){
    nxt[++cou] = head[a];
    head[a] = cou;
    end[cou] = b;
}

void dfs(int cur, int last){
    int max = 0;
    bool mt = true;
    for(int x = head[cur]; x != -1; x = nxt[x]){
        if(end[x] != last){
            dfs(end[x], cur);
            if(f[end[x]] > max){
                max = f[end[x]];
                mt = false;
            } else if(f[end[x]] == max)
                mt = true;
        }
    }
    if(mt)
        f[cur] = max + 1;
    else
        f[cur] = max;
}

void dfs2(int cur, int last){
    int max = 0;
    bool mt = true;
    int degree = 0;
    int pos;
    for(int x = head[cur]; x != -1; x = nxt[x]){
        if(end[x] != last){
            dfs(end[x], cur);
            if(f[end[x]] > max){
                max = f[end[x]];
                mt = false;
                pos = end[x];
            } else if(f[end[x]] == max)
                mt = true;
            degree++;
        }
    }
    // printf("%d %d pos = %d\n", cur, last, pos);
    if(degree == 0){
        type[++cnt] = 'B';
        num1[cnt] = cur;
        return ;
    }
    for(int x = head[cur]; x != -1; x = nxt[x]){
        if(end[x] != pos && end[x] != last){
            type[++cnt] = 'L';
            num1[cnt] = cur;
            type[++cnt] = 'M';
            num1[cnt] = cur;
            num2[cnt] = end[x];
            dfs2(end[x], cur);
        }
    }
    type[++cnt] = 'M';
    num1[cnt] = cur;
    num2[cnt] = pos;
    dfs2(pos, cur);
    if(mt)
        f[cur] = max + 1;
    else
        f[cur] = max;
}

int main(){
    memset(head, -1, sizeof(head));
    int n = inp();
    for(int i = 1; i < n; i++){
        int u = inp();
        int v = inp();
        link(u, v);
        link(v, u);
    }
    int root = 0;
    int min = INF;
    for(int i = 1; i <= n; i++){
        dfs(i, 0);
        if(f[i] < min){
            min = f[i];
            root = i;
        }
    }
    dfs2(root, 0);
    printf("%d\n%d\n", min, cnt + 1);
    printf("L %d\n", root);
    for(int i = 1; i <= cnt; i++){
        putchar(type[i]);
        printf(" %d", num1[i]);
        if(type[i] == 'M')
            printf(" %d", num2[i]);
        putchar('\n');
    }
}

拜拜! 

  • 19
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
P2375 [NOI2014] 动物园是一道经典的动态规划题目,以下是该题的详细题意和解题思路。 【题意描述】 有两个长度为 $n$ 的整数序列 $a$ 和 $b$,你需要从这两个序列中各选出一些数,使得这些数构成一个新的序列 $c$。其中,$c$ 序列中的元素必须在原序列中严格递增。每个元素都有一个价值,你的任务是选出的元素的总价值最大。 【解题思路】 这是一道经典的动态规划题目,可以采用记忆化搜索的方法解决,也可以采用递推的方法解决。 记忆化搜索的代码如下: ```c++ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN], a[MAXN], b[MAXN], n; int dfs(int x, int y) { if (dp[x][y] != -1) return dp[x][y]; if (x == n || y == n) return 0; int res = max(dfs(x + 1, y), dfs(x + 1, y + 1)); if (a[x] > b[y]) { res = max(res, dfs(x, y + 1) + b[y]); } return dp[x][y] = res; } int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); for (int i = 0; i < n; i++) scanf("%d", &b[i]); memset(dp, -1, sizeof(dp)); printf("%d\n", dfs(0, 0)); return 0; } ``` 其中,dp[i][j]表示选到a数组中第i个元素和b数组中第j个元素时的最大价值,-1表示未计算过。dfs(x,y)表示选到a数组中第x个元素和b数组中第y个元素时的最大价值,如果dp[x][y]已经计算过,则直接返回dp[x][y]的值。如果x==n或者y==n,表示已经遍历完一个数组,直接返回0。然后就是状态转移方程了,如果a[x] > b[y],则可以尝试选b[y],递归调用dfs(x, y+1)计算以后的最大价值。否则,只能继续遍历数组a,递归调用dfs(x+1, y)计算最大价值。最后,返回dp[0][0]的值即可。 递推的代码如下: ```c++ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN], a[MAXN], b[MAXN], n; int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); for (int i = 0; i < n; i++) scanf("%d", &b[i]); for (int i = n - 1; i >= 0; i--) { for (int j = n - 1; j >= 0; j--) { dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]); if (a[i] > b[j]) { dp[i][j] = max(dp[i][j], dp[i][j + 1] + b[j]); } } } printf("%d\n", dp[0][0]); return 0; } ``` 其中,dp[i][j]表示选到a数组中第i个元素和b数组中第j个元素时的最大价值。从后往前遍历数组a和数组b,依次计算dp[i][j]的值。状态转移方程和记忆化搜索的方法是一样的。 【参考链接】 P2375 [NOI2014] 动物园:https://www.luogu.com.cn/problem/P2375
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值