嘘~ 正在从服务器偷取页面 . . .

洛谷P2478 [SDOI2010]城市规划 题解


洛谷P2478 [SDOI2010]城市规划 题解

题目链接:P2478 [SDOI2010]城市规划

题意

给定一个带点权沙漠(多棵仙人掌),求最大权独立集。

此处独立集的定义为集合中任意两个结点的最短路径长度不小于 \(3\) (就是隔两个结点)

独立集的权值定义为独立集中所有的点的权值的和。

数据范围

\(n\le 10^6,m\le 2\times 10^6\),且点权 \(0\le h_i \le 1000\)

提示

原题给出的是点仙人掌组成的沙漠。

点仙人掌的定义为:每个点至多存在于一个简单环内的无向连通图。

边仙人掌的定义为:每条边至多存在于一个简单环内的无向连通图。

考虑这样一张图 (1,2) (1,3) (2,3) (3,4) (3,5) (4,5) 。它就是边仙人掌,而不是点仙人掌。

仙人掌dp套dp神题,想了我大概 \(5\) 个小时(其实是沙漠

我们可以用 \(\mathtt{Tarjan}\) 找环,然后分别考虑树上dp和环上dp。

\(f_{i,0/1/2}\) ,则

  • \(f_{i,0}\) 表示选了 \(i\)\(i\) 所在子树的最大价值
  • \(f_{i,1}\) 表示选了 \(i\) 的某个儿子, \(i\) 所在子树的最大价值
  • \(f_{i,2}\) 表示 \(i\) 和它儿子都没选,子树的最大价值

这里子树的定义为

  • \(u\) 不在环上或 \(u\) 是环顶,则子树定义为缩点后 \(\mathtt{DFS}\) 树上的子树,不考虑返祖边和横叉边。
  • \(u\) 在环上但不是环顶,则子树定义为 \(\mathtt{DFS}\) 树上「所有不在环上的儿子」的子树与 \(u\) 的并。

下文中使用 \(a \uparrow b+c\) 表示 a += b+c 。在讲解之前先给一张图方便理解。

树上dp部分

显然叶子结点有 \(f_{u,0}=h_u,~f_{u,1}=f_{u,2}=0\)

考虑转移。设 \(u\) 为非叶子结点, \(v \in \mathtt{son}(u)\) ,则 \[ \begin{aligned} f_{u,0} &\uparrow f_{v,2} \\[6pt]f_{u,2} &\uparrow \max\{f_{v,1},~f_{v,2}\} \end{aligned} \] 显然 \(f_{u,1}\) 需要在 \(f_{u,0},f_{u,2}\) 计算完再计算

因为恰好只能选一个儿子,所以我们可以按 \(f_{u,2}\) 的方式转移,并考虑选的那个儿子对应的增量,则 \[ f_{u,1} = f_{u,2} + \max\left\{f_{v,0} - \max\{f_{v,1}, ~ f_{v,2}\}\right\} \] 注意到后面那个式子是可以在算 \(f_{u,0}\)\(f_{u,2}\) 的时候算出来的。

因为用了 \(\mathtt{Tarjan}\) ,所以在做 \(u\) 的树上dp部分时,不会把 \(u\) 的环上dp部分给多算进去。


环上dp部分

设环顶为 \(u\) (即 \(\mathtt{DFS}\) 树上深度最小的那个点)

记环上点按深度大到小依次为 \(c_1,c_2,\cdots,c_k\) ,则有 \(c_k = u\)

\(\mathtt{calc}(l,r)\) 表示规定了环上 \(c_{l-1},c_{r+1}\) 不能选,他们之间的结点 \(c_{l\sim r}\)按照题意随便选的最大价值。

类似于 \(f\) 的定义,对于每个要处理的环,我们设一个 \(g_{i,0/1/2}\)

  • \(g_{i,0}\) 表示选了 \(c_i\)\(c_i\) 所在子树的最大价值。
  • \(g_{i,1}\) 表示选了 \(c_i\) 的某个儿子,\(c_i\) 所在子树的最大价值。
  • \(g_{i,2}\) 表示 \(c_i\) 和儿子都没选,\(c_i\) 所在子树的最大价值

边界:\(g_{l-1,0} = 0, ~g_{l-1,1} = f_{c_{l-1},1},~g_{l-1,2} = f_{c_{l-1},2}\)

考虑转移,不难发现有 \[ \begin{aligned} g_{i,0} &= g_{i-1,2} + f_{c_i,0} \\[6pt] g_{i,2} &= \max\left\{g_{i-1,1},~g_{i-1,2}\right\} + f_{c_i,2} \\[6pt] g_{i,1} &= \max\left\{g_{i-1,0} + f_{c_i,2}, ~\max\{g_{i-1,1},~g_{i-1,2}\} + f_{c_i,1}\right\} \end{aligned} \] 注意 \(l \le i \le r+1\)\(\mathtt{calc}(l,r) = \max\{g_{r+1,1},~g_{r+1,2}\}\)

特别地,如果 \(l > r+1\) ,则定义 \(\mathtt{calc}(l,r)=0\)

接着考虑 \(f_{u,0/1/2}\) (环顶)的转移,不难发现(注意这里还是增量,因为可能不止一个儿子在环上) \[ \begin{aligned} f_{u,0} &\uparrow f_{c_1,2} + f_{c_{k-1},2} + \mathtt{calc}(3,k-3) \\[6pt] f_{u,2} &\uparrow \mathtt{calc}(2,k-2) \end{aligned} \] \(f_{u,1}\) 不同于树上dp部分,要在 \(f_{u,0},f_{u,2}\) 之前算。

  • 选择了环外子结点,则 \(f_{u,1} \gets f_{u,1} + \mathtt{calc}(2,k-2)\)
  • 选择了 \(c_1\) ,则 \(f_{u,1} \gets f_{u,2} + f_{c_2,2} + f_{c_1,0} + \mathtt{calc}(4,k-2)\)
  • 选择了 \(c_{k-1}\) ,则 \(f_{u,1} \gets f_{u,2} + f_{c_{k-2},2} + f_{c_{k-1},0} + \mathtt{calc}(2,k-4)\)

综上,有 \[ f_{u,1} \gets \max\begin{cases}f_{u,1} + \mathtt{calc}(2,k-2) \\[6pt]f_{u,2} + f_{c_2,2} + f_{c_1,0} + \mathtt{calc}(4,k-2) \\[6pt]f_{u,2} + f_{c_{k-2},2} + f_{c_{k-1},0} + \mathtt{calc}(2,k-4) \end{cases} \] 时间复杂度 \(\mathcal{O}(n+m)\)

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
namespace FastIO
{
    #define gc() readchar()
    #define pc(a) putchar(a)
    #define SIZ (int)(1e6+15)
    char buf1[SIZ],*p1,*p2;
    char readchar()
    {
        if(p1==p2)p1=buf1,p2=buf1+fread(buf1,1,SIZ,stdin);
        return p1==p2?EOF:*p1++;
    }
    template<typename T>void read(T &k)
    {
        char ch=gc();T x=0,f=1;
        while(!isdigit(ch)){if(ch=='-')f=-1;ch=gc();}
        while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=gc();}
        k=x*f;
    }
    template<typename T>void write(T k)
    {
        if(k<0){k=-k;pc('-');}
        static T stk[66];T top=0;
        do{stk[top++]=k%10,k/=10;}while(k);
        while(top){pc(stk[--top]+'0');}
    }
    template<typename T>void print(T x) { write(x); pc('\n'); }
}using namespace FastIO;
#define N ((int)(1e6+15))

int n,m,tim,pos=1,head[N],tmp[N][3],h[N],c[N],f[N][3],fa[N],dfn[N],low[N];
struct Edge{ int u,v,next; } e[N * 2];
void addEdge(int u,int v) { e[++pos] = {u,v,head[u]}; head[u] = pos; }
int calc(int l,int r)
{
    if(l > r+1) return 0;
    tmp[l-1][0] = 0;
    tmp[l-1][1] = f[c[l-1]][1];
    tmp[l-1][2] = f[c[l-1]][2];
    for(int i=l; i<=r+1; i++)
    {
        tmp[i][0] = tmp[i-1][2] + f[c[i]][0];
        tmp[i][2] = max(tmp[i-1][1], tmp[i-1][2]) + f[c[i]][2];
        tmp[i][1] = max(tmp[i-1][0] + f[c[i]][2], max(tmp[i-1][1],tmp[i-1][2]) + f[c[i]][1]);
    }
    // cout << l << "  " << r << "  " << max(tmp[r+1][1],tmp[r+1][2]) << '\n';
    return max(tmp[r+1][1], tmp[r+1][2]);
}
void solve(int s,int t)
{
    int cnt = 0;
    for(c[++cnt] = s; c[cnt] != t; ++cnt)
        c[cnt + 1] = fa[c[cnt]];

    int mx = max(f[c[1]][0] + f[c[2]][2] + calc(4,cnt-2),
         f[c[cnt-1]][0] + f[c[cnt-2]][2] + calc(2,cnt-4));
    f[t][1] = max(f[t][1] + calc(2,cnt-2), f[t][2] + mx);
    f[t][0] += calc(3, cnt-3) + f[c[1]][2] + f[c[cnt-1]][2];
    f[t][2] += calc(2, cnt-2);
}
void Tarjan(int u,int Fa)
{
    fa[u] = Fa; dfn[u] = low[u] = ++tim;
    f[u][0] = h[u]; f[u][2] = 0;
    int mx = 0;
    for(int i=head[u]; i; i=e[i].next)
    {
        int v = e[i].v;
        if(!dfn[v])
        {
            Tarjan(v,u); down(low[u], low[v]);
        }else if(v != Fa)
        { down(low[u], dfn[v]); }
        if(low[v] > dfn[u])
        {
            f[u][0] += f[v][2];
            f[u][2] += max(f[v][2], f[v][1]);
            up(mx, f[v][0] - max(f[v][2], f[v][1]));
        }
    }
    f[u][1] = f[u][2] + mx;
    for(int i=head[u]; i; i=e[i].next)
    {
        int v = e[i].v;
        if(low[v] == dfn[u] && fa[v] != u) solve(v,u);
    }
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    read(n); read(m);
    for(int i=1; i<=n; i++) read(h[i]);
    for(int i=1,u,v; i<=m; i++)
    {
        read(u); read(v);
        addEdge(u,v); addEdge(v,u);
    }
    int ans = 0;
    for(int i=1; i<=n; i++) if(!dfn[i])
    { Tarjan(i,0); ans += max({f[i][0], f[i][1], f[i][2]}); }
    print(ans);
    return 0;
}

参考文献

[1] https://www.luogu.com.cn/blog/ix-35/solution-p2478


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录