dag的半连通分量+dp

该博客探讨了如何在有向无环图中删除边,使得所有顶点的入度和出度减少或为0,并求解在这种情况下最大可能的「可爱」集合(即在非删除边上的强连通子图)的大小。文章通过拓扑排序和动态规划给出了解决方案,并提供了输入输出示例及代码实现。

G. Remove Directed Edges

time limit per test

2 seconds

memory limit per test

256 megabytes

input

standard input

output

standard output

You are given a directed acyclic graph, consisting of nn vertices and mm edges. The vertices are numbered from 11 to nn. There are no multiple edges and self-loops.

Let invinv be the number of incoming edges (indegree) and outvoutv be the number of outgoing edges (outdegree) of vertex vv.

You are asked to remove some edges from the graph. Let the new degrees be in′vin′v and out′vout′v.

You are only allowed to remove the edges if the following conditions hold for every vertex vv:

  • in′v<invin′v<inv or in′v=inv=0in′v=inv=0;
  • out′v<outvout′v<outv or out′v=outv=0out′v=outv=0.

Let's call a set of vertices SS cute if for each pair of vertices vv and uu (v≠uv≠u) such that v∈Sv∈S and u∈Su∈S, there exists a path either from vv to uu or from uu to vv over the non-removed edges.

What is the maximum possible size of a cute set SS after you remove some edges from the graph and both indegrees and outdegrees of all vertices either decrease or remain equal to 00?

Input

The first line contains two integers nn and mm (1≤n≤2⋅1051≤n≤2⋅105; 0≤m≤2⋅1050≤m≤2⋅105) — the number of vertices and the number of edges of the graph.

Each of the next mm lines contains two integers vv and uu (1≤v,u≤n1≤v,u≤n; v≠uv≠u) — the description of an edge.

The given edges form a valid directed acyclic graph. There are no multiple edges.

Output

Print a single integer — the maximum possible size of a cute set SS after you remove some edges from the graph and both indegrees and outdegrees of all vertices either decrease or remain equal to 00.

#include<iostream>
#include<string>
#include<cstring>
using namespace std;
const int N=2e5+5;
int h[N],e[N],ne[N],idx;
int din[N],dout[N],d[N];
int q[N];
int dp[N];
int n,m;
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void topsort()
{
    int hh=0,tt=-1;
    for(int i=1;i<=n;i++)
    {
        if(!d[i]) q[++tt]=i;
    }
    while(hh<=tt)
    {
        int now=q[hh++];
        for(int i=h[now];~i;i=ne[i])
        {
            int to=e[i];
            if(dout[now]>=2&&din[to]>=2) dp[to]=max(dp[to],dp[now]+1);
            if(--d[to]==0) q[++tt]=to;
        }
    }
}
int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);
    for(int i=1;i<=n;i++) dp[i]=1;
    for(int i=1;i<=m;i++)
    {
        int a,b;
        cin>>a>>b;
        add(a,b);
        din[b]++,dout[a]++,d[b]++;
    }
    topsort();
    int res=0;
    for(int i=1;i<=n;i++) res=max(res,dp[i]);
    cout<<res<<endl;
}

### 强连通分量缩点算法模板 强连通分量(Strongly Connected Component, SCC)是指在一个有向图中,任意两个节点都可以互相到达的最大子图。通过 Tarjan 算法可以高效地找到这些强连通分量并将其缩成单个节点,从而简化原图结构。 以下是基于 Tarjan 算法的 C++ 实现代码模板: ```cpp #include <bits/stdc++.h> using namespace std; const int MAXN = 1e5 + 5; // 节点数量上限 int n, m; vector<int> adj[MAXN]; // 邻接表存储图 stack<int> stk; // 辅助栈 bool in_stack[MAXN]; // 判断当前节点是否在栈中 int dfn[MAXN], low[MAXN]; // 时间戳数组和追溯值数组 int scc_id[MAXN]; // 存储每个节点所属的强连通分量编号 int idx = 0, cnt_scc = 0; // 当前时间戳计数器 和 强连通分量计数器 void tarjan(int u) { dfn[u] = low[u] = ++idx; // 初始化dfn和low stk.push(u); // 将u压入栈 in_stack[u] = true; // 标记u已在栈中 for (auto &v : adj[u]) { // 枚举所有邻接点 if (!dfn[v]) { // 如果v未访问过 tarjan(v); low[u] = min(low[u], low[v]); // 更新low[u] } else if (in_stack[v]) { // 如果v已经在栈中 low[u] = min(low[u], dfn[v]); } } if (dfn[u] == low[u]) { // 找到一个新的强连通分量 ++cnt_scc; // 新增一个SCC编号 while (true) { // 提取该SCC中的所有节点 int v = stk.top(); stk.pop(); in_stack[v] = false; scc_id[v] = cnt_scc; // 记录属于哪个SCC if (v == u) break; // 当弹出的是u时结束循环 } } } // 主函数调用部分 void solve() { cin >> n >> m; // 输入节点数和边数 for (int i = 1; i <= m; ++i) { int u, v; cin >> u >> v; // 输入一条有向边 adj[u].push_back(v); } memset(dfn, 0, sizeof(dfn)); // 初始化dfn数组 memset(in_stack, 0, sizeof(in_stack)); idx = cnt_scc = 0; for (int i = 1; i <= n; ++i) { // 对每个节点运行tarjan if (!dfn[i]) tarjan(i); } cout << "Total Strongly Connected Components: " << cnt_scc << "\n"; } ``` #### 关键说明 上述代码实现了 Tarjan 算法的核心逻辑[^2]。 - `dfn` 数组记录了每个节点第一次被访问的时间戳。 - `low` 数组用于追踪能够回溯到的最早祖先节点的时间戳。 - 使用辅助栈来保存当前路径上的节点,并判断哪些节点已经形成一个完整的强连通分量。 - 每次当发现某个节点满足条件 `dfn[u] == low[u]` 时,表示找到了一个新的强连通分量[^3]。 完成 Tarjan 算法后,可以通过构建新的缩点后的图来进行进一步处理。具体做法如下: - 创建新图,其中每个节点代表原始图的一个强连通分量。 - 统计每条边连接的不同强连通分量之间的关系,忽略内部重复边。 --- ### 缩点后的应用实例 假设我们需要计算拓扑序或者解决某些动态规划问题,在缩点之后的新图上操作会更加简单有效。例如,对于 DAG 上的经典 DP 或最短路问题,可以直接在此基础上展开分析。 ```cpp vector<vector<int>> new_adj(cnt_scc + 1); // 缩点后的新图 long long indegree[cnt_scc + 1] = {}; // 入度统计 for (int u = 1; u <= n; ++u) { for (auto &v : adj[u]) { if (scc_id[u] != scc_id[v]) { // 只考虑跨不同SCC的边 new_adj[scc_id[u]].push_back(scc_id[v]); indegree[scc_id[v]]++; } } } ``` 此段代码展示了如何根据原有图生成缩点后的图结构[^1]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值