洛谷 P2272 [ZJOI2007]最大半连通子图 解题报告

本文针对ZJOI2007最大半连通子图问题提供了一种解决方案,通过缩点处理和动态规划算法找到最大的半连通子图及其数量。

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

P2272 [ZJOI2007]最大半连通子图

题目描述

一个有向图\(G=(V,E)\)称为半连通的\((Semi-Connected)\),如果满足:\(\forall u,v \in V\),满足\(u \to v\)\(v \to u\),即对于图中任意两点\(u\)\(v,\)存在一条\(u\)\(v\)的有向路径或者从\(v\)\(u\)的有向路径。若\(G'=(V',E')\)满足\(V' \in V\)\(E'\)\(E\)中所有跟\(V'\)有关的边,则称\(G'\)\(G\)的一个导出子图。若\(G'\)\(G\)的导出子图,且\(G'\)半连通,则称\(G'\)\(G\)的半连通子图。若\(G'\)\(G\)所有半连通子图中包含节点数最多的,则称\(G'\)\(G\)的最大半连通子图。给定一个有向图\(G\),请求出\(G\)的最大半连通子图拥有的节点数\(K\),以及不同的最大半连通子图的数目\(C\)。由于\(C\)可能比较大,仅要求输出\(C\)\(X\)的余数。

输入输出格式

输入格式:

第一行包含两个整数\(N\)\(M\)\(X\)\(N\)\(M\)分别表示图\(G\)的点数与边数,X的意义如上文所述接下来\(M\)行,每行两个正整数\(a\),\(b\),表示一条有向边\((a,b)\)。图中的每个点将编号为\(1,2,3…N\),保证输入中同一个\((a,b)\)不会出现两次。

输出格式:

应包含两行,第一行包含一个整数\(K\)。第二行包含整数\(C\) \(Mod\) \(X\).

说明

对于100%的数据, \(N \le 100000, M \le 1000000, X \le 10^8\)


这种题先缩点都成套路了吧

考虑在一个有向无环图中半联通图是什么,结果是一条链,直接做DP就行了

但是!!

这个题一定要考虑重边


Code:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define ll long long
using namespace std;
const int N=100010;
const int M=1000010;
int head0[N],Next0[M],to0[M],cnt0;
void add0(int u,int v)
{
    Next0[++cnt0]=head0[u];to0[cnt0]=v;head0[u]=cnt0;
}
int head[N],Next[M],to[M],cnt;
void add(int u,int v)
{
    Next[++cnt]=head[u];to[cnt]=v;head[u]=cnt;
}
int dfn[N],low[N],in[N],s[N],ha[N],siz[N],time,tot;
int n,m,n_,cntt;
ll p;
pair <int,int > e[M];
void tarjan(int now)
{
    dfn[now]=low[now]=++time;
    s[++tot]=now;
    in[now]=1;
    for(int i=head0[now];i;i=Next0[i])
    {
        int v=to0[i];
        if(!dfn[v])
        {
            tarjan(v);
            low[now]=min(low[now],low[v]);
        }
        else if(in[v])
            low[now]=min(low[now],dfn[v]);
    }
    if(low[now]==dfn[now])
    {
        int k,Siz=0;
        n_++;
        do
        {
            k=s[tot--];
            Siz++;
            ha[k]=n_;
            in[k]=0;
        }while(k!=now);
        siz[n_]=Siz;
    }
}
void New()
{
    for(int i=1;i<=n;i++)
    {
        for(int j=head0[i];j;j=Next0[j])
        {
            int v=to0[j];
            if(ha[v]!=ha[i])
                e[++cntt]=make_pair(ha[i],ha[v]);
        }
    }
    sort(e+1,e+1+cntt);
    cntt=unique(e+1,e+1+cntt)-(e+1);
    for(int i=1;i<=cntt;i++)
    {
        in[e[i].second]++;
        add(e[i].first,e[i].second);
    }
}
void init()
{
    scanf("%d%d%lld",&n,&m,&p);
    int u,v;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&u,&v);
        add0(u,v);
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])
            tarjan(i);
    memset(in,0,sizeof(in));
    New();
}
ll Cnt[N],ans;
int dp[N],mx;
queue <int > q;
void work()
{
    for(int i=1;i<=n_;i++)
        if(!in[i])
        {
            q.push(i);
            dp[i]=siz[i];
            mx=max(mx,dp[i]);
            Cnt[i]=1;
        }
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(int i=head[u];i;i=Next[i])
        {
            int v=to[i];
            if(dp[v]<dp[u]+siz[v])
            {
                dp[v]=dp[u]+siz[v];
                Cnt[v]=Cnt[u];
                mx=max(mx,dp[v]);
            }
            else if(dp[v]==dp[u]+siz[v])
                (Cnt[v]+=Cnt[u])%=p;
            in[v]--;
            if(!in[v]) q.push(v);
        }
    }
    for(int i=1;i<=n_;i++)
        if(dp[i]==mx)
            (ans+=Cnt[i])%=p;
    printf("%d\n%lld\n",mx,ans);
}
int main()
{
    init();
    work();
    return 0;
}

2018.7.26

转载于:https://www.cnblogs.com/butterflydew/p/9369719.html

<think>嗯,用户问的是洛谷P1122最大树和的解题思路,涉及到数据结构、树形DP和算法实现。首先,我需要回忆一下这个题目的具体要求。题目要求通过修剪得到最大的美丽指数和,也就是在树中选择一个连通的树,使得节点值的和最大。这应该是一个典型的树形DP问题。 根据引用[1],树形DP通常是在树上做动态规划,递归处理树的信息。所以基本步骤应该是把树转为有根树,然后DFS遍历,从叶节点向上汇总信息。这里的关键是设计DP状态和转移方程。 引用[2]提到这道题需要将花卉结构抽象为树,用树形DP和动态规划。具体步骤包括构建树的数据结构,然后DFS计算每个节点的最大树和。我需要确定DP状态的定义,比如dp[u]表示以u为根的树中的最大和,包括选择u的情况。 然后考虑转移方程。对于每个节点u,遍历其节点v,如果v的dp值大于0,就加上,否则不加。这样dp[u] = value[u] + sum(max(dp[v], 0))。这样就能保证每个节点的贡献是正的,否则不选该树。 数据结构方面,通常用邻接表来存储树的结构。每个节点保存其节点的列表。在代码实现时,需要注意避免重复访问父节点,所以DFS的时候需要记录父节点。 然后需要考虑初始化,每个节点的初始dp值就是它自身的值。然后递归处理所有节点,更新dp[u]的值。 另外,题目中的树可能有多个分支,所以最终的答案不一定是根节点的dp值,而是所有节点dp值中的最大值。因此,在遍历过程中需要维护一个全局最大值。 引用[3]中的代码实现部分可能有帮助,比如如何处理DFS和状态转移。例如,在遍历节点时,计算它们的dp值,然后决定是否加上。 还有,边界条件处理,比如叶节点,它的节点处理完后,dp值就是它本身的值加上节点中正的贡献,但实际上叶节点没有节点,所以直接返回自己的值。 测试样例可能需要考虑有正有负的情况,比如某个树的和为负,这时候应该剪掉,不选择该树。 总结步骤:1. 构建树的邻接表;2. DFS遍历,计算每个节点的dp值;3. 在过程中记录最大值;4. 输出最大值。 现在需要把这些步骤详细地解释出来,确保用户能理解每个部分的作用。例如,邻接表如何构建,如何避免访问父节点,DP状态转移的逻辑,以及如何维护全局最大值。 可能还需要注意一些细节,比如节点编号是否从1开始,输入的处理方式,以及如何处理较大的数据量,但题目中的具体数据规模可能根据引用[4]中的说明,绝对值不超过2147483647,所以用int应该足够,但代码中可能需要用长整型以防万一。 最后,确保代码示例正确,用Python的话可能需要递归实现DFS,但需要注意Python的递归深度限制,不过题目中的数据范围可能不会太大。或者用户可能希望用其他语言,但根据用户提供的示例,Python代码是可行的。</think>### 洛谷 P1122 最大树和 解题思路 #### 问题分析 题目要求在一棵树中选择一个**连通树**,使得该树所有节点的“美丽指数”之和最大。这本质上是树形动态规划(Tree DP)的典型问题,通过状态转移计算每个树的最优解。 #### 核心思路 1. **状态定义** 定义 $dp[u]$ 表示以节点 $u$ 为根的树中,**必须包含 $u$** 的最大美丽指数和。 -节点的 $dp[v] > 0$,则选择该树(累加收益) - 若 $dp[v] \leq 0$,则舍弃该树(避免损失) 2. **转移方程** $$dp[u] = \text{value}[u] + \sum_{\text{节点 }v} \max(dp[v], 0)$$ 3. **全局最大值** 最终的答案并非直接取根节点的 $dp$ 值,而是所有节点的 $dp[u]$ 中的最大值,因为最优解可能存在于某棵树中。 #### 数据结构与算法实现 1. **树的存储** 使用**邻接表**存储树结构,例如: ```python tree = [[] for _ in range(n+1)] # 节点编号从1开始 for _ in range(n-1): u, v = map(int, input().split()) tree[u].append(v) tree[v].append(u) ``` 2. **DFS递归遍历** - **递归终止条件**:叶节点(无节点) - **关键操作**:遍历节点时跳过父节点,避免回路 ```python def dfs(u, parent): dp[u] = value[u] # 初始化dp[u]为节点自身值 for v in tree[u]: if v == parent: # 避免回溯父节点 continue dfs(v, u) # 递归处理节点 if dp[v] > 0: # 仅当节点贡献为正时累加 dp[u] += dp[v] nonlocal max_sum max_sum = max(max_sum, dp[u]) # 更新全局最大值 ``` 3. **初始化与调用** ```python value = [0] + list(map(int, input().split())) # 假设输入为节点1~n的值 dp = [0] * (n+1) max_sum = -float('inf') dfs(1, -1) # 假设根节点为1,父节点标记为-1 print(max_sum) ``` #### 复杂度分析 - **时间复杂度**:$O(n)$,每个节点仅访问一次 - **空间复杂度**:$O(n)$,存储树和DP数组 #### 示例解释 假设输入树结构如下(节点值为 $[1, 2, 3, 4, 5]$): ``` 1 ├─2 └─3 ├─4 └─5 ``` - 节点3的 $dp$ 计算:$3 + \max(4, 0) + \max(5, 0) = 12$ - 全局最大值可能出现在以3为根的树中[^2][^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值