【IOI2014】Friend

动态规划,思维题

题目大意:

有 $n$ 个人,编号为 $0\sim n-1$。现在要建立一个关系网。

一开始只有 $0$ 号在关系网内,现在要将 $1\sim n-1$ 号依次加入。

加入 $i$ 的时候,会选择一个 $0\sim i-1$ 的人当做主持人。然后有三种情况:

  1. IAmYourFriend,$i$ 号将和他的主持人成为朋友。

  2. MyFriendsAreYourFriends,$i$ 号将和当前时刻他的主持人的朋友成为朋友(和主持人不成为朋友)。

  3. WeAreYourFriends,$i$ 号将和当前时刻他的主持人以及主持人的朋友成为朋友。

每个人有一个正的可信度。

现在要在最终的关系网中选择若干个人,满足两两之间都不是朋友。

求所有选法中,可信度之和的最大值。

题解:

很妙的题。

题目相当于求最终建出来的图的最大独立集。

对于每个点,我们将它的主持人看作它的父亲,那么所有点形成了一棵以 $0$ 为根的有根树。

树的最大独立集可以通过简单的 DP 得到,对于本题,也可以尝试自底向上地进行 DP。

定义 $p_i$ 表示 $i$ 的主持人编号。令 $f_i,g_i$ 分别表示对编号大于等于 $i$ 的结点中,选、不选 $i$ 号点时的最大独立集大小。

考虑 $u$ 与 $x=p_u$ 的关系。

  1. 这种情况最为简单。若 $x$ 选了,则 $u$ 不能选。若 $x$ 不选,则 $u$ 可选可不选。

    $f_x=f_x+g_u$,$g_x=g_x+\max(f_u,g_u)$。

  2. 这种情况比较复杂。

    若 $x$ 选了,则 $u$ 可选可不选。

    若 $x$ 没选,$u$ 也没选,则没有任何影响。

    若 $x$ 没选,$u$ 选了,就需要进行考虑,因为此时比 $u$ 编号小的,且是 $x$ 的朋友的点都不能选了。

    由于我们是自底向上考虑转移的,因此可以按照编号从大到小转移。那么转移到 $u$ 的时候,比 $u$ 编号大的都已经转移完毕了。所以我们可以认为编号大于 $u$ 的点都已经被删掉,不需要考虑。

    这个时候,若 $u$ 选了而 $x$ 没选,则与 $x$ 直接连边的点都是不能选的。那么我们就可以认为是选了 $x$ 而导致了与 $x$ 有连边的点不能选,将 $g_x+f_u$ 转移状态到 $f_x$ 上即可。由于 $u$ 之后的状态都已经转移过了,因此不会产生影响。

    那么转移为:$f_x=\max(f_x+\max(f_u,g_u),g_x+f_u)$,$g_x=g_x+g_u$。

  3. 和第二种情况大致相同,只是在 $x$ 选的时候不能选 $u$ 了。

    $f_x=\max(f_x+g_u,g_x+f_u)$,$g_x=g_x+g_u$。

初值 $g_i=0,f_i=\mathrm{confidence}[i]$。

最后答案取 $\max(f_0,g_0)$ 即可。

时空复杂度 $O(n)$。

Code:

#include"friend.h"
#include<algorithm>
int g[100005];
inline void chkmax(int&a,int b){a=std::max(a,b);}
int findSample(int n,int*f,int*p,int*tp){
    for(int i=n-1;i;--i)
    switch(tp[i]){
        case 0:f[p[i]]+=g[i],g[p[i]]+=std::max(f[i],g[i]);break;
        case 1:chkmax(f[p[i]]+=std::max(f[i],g[i]),g[p[i]]+f[i]),g[p[i]]+=g[i];break;
        case 2:chkmax(f[p[i]]+=g[i],g[p[i]]+f[i]),g[p[i]]+=g[i];
    }
    return std::max(*f,*g);
}