luogu【P3387】【模板】缩点

原题入口

PS:这个题数据是由Hany01大大出出来的 %%%

这个题显然是一道强联通+DAGdp的题 (题目背景有= =)

缩点的原因就是:不缩会一直在一个地方绕圈圈 而且不能进行后面的DAPdp 而且给你的所有点权全是正的

我在这用的是Tarjan(因为他发明算法太多了233)

这个dp方程比较容易找 令dp[u] 表示 缩点后 第u个强联通分量为起点的路径上点和的最大值

所以有 dp[u] = max{dp[v]} + val[u]  (这里v是指u出发可以到达的点)

由于是DAGdp 所以我们一开始要从入度为0的点出发 一直向下走 回溯的时候更改这个节点的dp值

下附程序(Tarjan讲解见程序):

#include <bits/stdc++.h>
#define For(i, l, r) for(int i = (l); i <= (int)(r); ++i)
#define Fordown(i, r, l) for(int i = (r); i >= (int)(l); --i)
#define Set(a, v) memset(a, v, sizeof(a))
using namespace std;

inline int read() {
    int x = 0, fh = 1; char ch;
    for (; !isdigit(ch); ch = getchar()) if (ch == ‘-‘) fh = -1;
    for (; isdigit(ch); ch = getchar()) x = (x<<1) + (x<<3) + (ch^‘0‘);
    return x * fh;
}

#include <stack>
const int N = 1e4+1e2, M = 1e5*2+1e2; //点的范围和边的范围 开大点防止数据bug
struct edge {
    int to[M], Next[M], Head[N], e;
    void init() {Set(Head, 0); e = 0;}
}; //用链式前向星存边,访问更加迅速
edge G1, G2; //结构体便于多图 

void add_edge (int u, int v, edge &G) { //引用G为了更新G
    G.to[++G.e] = v;
    G.Next[G.e] = G.Head[u];
    G.Head[u] = G.e;
} //加入一条有向边

#define Travel(i, u, G) for(int i = G.Head[u]; i; i = G.Next[i])
//链式前向星的遍历方式 i表示在G图中,所有从u出发的边 (宏定义偷懒233)
int pre[N], lowlink[N], sccno[N], dfs_clock = 0, scc_cnt = 0;
stack<int> S;
//Tarjan所需要的一些变量,时间戳dfs_clock,最早进入时间pre,能访问最远祖先lowlink
//强联通分量总数scc_cnt,每个点所属强联通分量编号sccno
int val[N], val1[N];
//val表示原图中的权值,val1表示缩点后每个强联通分量的权值
void Tarjan (int u) { //Tarjan主体(访问u)
    pre[u] = lowlink[u] = ++dfs_clock; //第一次进入 把时间和最远到达祖先设为u
    S.push(u); //把u放入栈中
    Travel (i, u, G1) { //遍历原图从u开始的节点
        int v = G1.to[i]; //取出到达的节点
        if (!pre[v]) { //没有访问过
            Tarjan (v); //递归向下继续搜
            lowlink[u] = min(lowlink[u], lowlink[v]); //更新能到达最远祖先的值
        } else if (!sccno[v]) //不是别的强联通分量中的点 且 已经访问过了
            lowlink[u] = min(lowlink[u], pre[v]); //更新最远祖先(因为v在u之前被访问)证明有环
    }
    if (lowlink[u] == pre[u]) { //能到达的最远祖先就是自己
        ++scc_cnt; //将总数增加
        for(;;) { //不断递归
            int now = S.top(); S.pop(); //取出当前栈顶的数
            sccno[now] = scc_cnt; //标记点的编号
            val1[scc_cnt] += val[now]; //统计强联通分量的权值
            if (now == u) break; //到了自己就停下来 不可能继续向上走了
        }
    }
}

int in_deg[N]; //记录入度
int max_ans = 0; //答案
int dp[N]; //dp数组
bool vis[N]; //标记是否到达
void dfs(int u) {
    if (vis[u]) return;
    vis[u] = true; //似乎不要也行 但好像更慢
    dp[u] = val1[u]; //初始化标记为强联通得权值
    Travel (i, u, G2) { //从缩点后的图开始遍历
        int v = G2.to[i]; //取出到达点
        dfs(v); //向下遍历
        dp[u] = max(dp[u], dp[v] + val1[u]); //回溯时更新dp值
    }
    max_ans = max(max_ans, dp[u]); //更新答案
}

int main() {
    int n = read(), m = read();
    G1.init(); G2.init();
    For (i, 1, n)
        val[i] = read();
    while (m--) {
        int u = read(), v = read();
        add_edge (u, v, G1); //把边加入G1中
    }
    For (i, 1, n)
        if (!pre[i]) Tarjan (i); //搜索强联通 

    For (i, 1, n)
        Travel (j, i, G1) { //遍历所有的边
            int v = G1.to[j];
            if (sccno[i] == sccno[v]) continue; //如果在同一个强联通分量中就向下走
            add_edge (sccno[i], sccno[v], G2); //把新边加入G2中
            ++in_deg[sccno[v]]; //增加v入度
        }

    For (i, 1, scc_cnt)
        if (!in_deg[i]) dfs(i); //搜索入度为0的点
    printf ("%d\n", max_ans);
}
时间: 2024-10-13 15:31:39

luogu【P3387】【模板】缩点的相关文章

[luogu P3384] [模板]树链剖分

[luogu P3384] [模板]树链剖分 题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z 操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和 操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z 操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和 输入输出格式 输入格式: 第一行包含4个正整数

luogu P3919 [模板]可持久化数组(可持久化线段树/平衡树)(主席树)

luogu P3919 [模板]可持久化数组(可持久化线段树/平衡树) 题目 #include<iostream> #include<cstdlib> #include<cstdio> #include<cmath> #include<cstring> #include<iomanip> #include<algorithm> #include<ctime> #include<queue> #inc

luogu P3387 【模板】缩点_拓扑排序

Code: #include <stack> #include <cstdio> #include <algorithm> #include <queue> #include <cstring> #include <map> #define setIO(s) freopen(s".in","r",stdin) using namespace std; namespace Tarjan{ #def

Luogu【模板】树状数组

https://www.luogu.org/problemnew/show/P3374 单点修改, 区间查询 1 //2018年2月18日17:58:16 2 #include <iostream> 3 #include <cstdio> 4 using namespace std; 5 6 const int N = 500001; 7 int n, m; 8 int a[N], c[N]; 9 10 inline int lowbit(int x){ 11 return x &

#50: Luogu 2485 模板

$des$ 1.给定y.z.p,计算y^z mod p 的值: 2.给定y.z.p,计算满足xy ≡z(mod p)的最小非负整数x: 3.给定y.z.p,计算满足y^x ≡z(mod p)的最小非负整数x. $sol$ 模板+模板+模板 #include <bits/stdc++.h> using namespace std; #define LL long long LL n, k; LL Ksm(LL a, LL b, LL p) { LL ret = 1; while(b) { if(

[luogu P5325][模板]Min_25筛

Address Luogu #5325 Solution 记 \(p_i\) 表示第 \(i\) 小的质数(\(p[0]=1\)),\(s1[x]=\sum_{i=1}^{x}p[x],s2[x]=\sum_{i=1}^{x}p[x]^2\). 记 \(g1(x,i)\) 为:\[\sum_{j=1}^{x}[j是质数或j的最小质因子大于p_i]j\] 记 \(g2(x,i)\) 为:\[\sum_{j=1}^{x}[j是质数或j的最小质因子大于p_i]j^2\] 因为 \(n\) 以内的合数的

Luogu P3811 [模板]乘法逆元 题解报告

题目传送门 [题目大意] 给定$n$,求$1-n$在膜$p$意义下的乘法逆元. [思路分析] 好的原本我只会求单个数的逆元,然后被告知了这道题之后发现自己不会做(我果然还是太弱了),于是就学了一下递推求逆元. 设$p=k*i+r$,则可得$k*i+r\equiv0(mod\ p)$,然后乘上$i^{-1},r^{-1}$即可得到$k*r^{-1}+i^{-1}\equiv0(mod\ p)$ 由于$k=\lfloor \frac{p}{i}\rfloor,r=p\ mod\ i$,所以$i^{-

[题解] Luogu P4245 [模板]任意模数NTT

三模NTT 不会... 都0202年了,还有人写三模NTT啊... 讲一个好写点的做法吧: 首先取一个阀值\(w\),然后把多项式的每个系数写成\(aw + c(c < w)\)的形式,换句话说把多项式\(f(x)\)写成两个多项式相加的形式: \[ f(x) = wf_0(x) + f_1(x) \] 这样在这道题中取\(W = 2^{15}\)就可以避免爆long long了. 乘起来的话就是 \[ f \cdot g = (w f_0 + f_1)(wg_0 + g_1) = (f_0 g

【Luogu P2002&amp;P2341】消息扩散/受欢迎的奶牛

Luogu P2002 Luogu P2341 使用强连通分量算法缩点 第一题统计入度为0的个数强连通分量数. 第二题的答案为当且仅当仅有一个强连通分量的出度为0时该强连通分量的节点数,原因如下:若一个强连通分量出度为0,则说明这个强连通分量的喜爱无法对外传递:若有多个强连通分量出度为0,则说明这几个强连通分量的喜爱无法对外传递,则不可能产生明星. 有关强连通分量的知识,可以看我的另一篇博客 [Luogu P3387]缩点模板(强连通分量Tarjan&拓扑排序) AC代码 P2002: #inc

【Luogu P2515】软件安装

Luogu P2515 这道题的题面与P2146有点像.一些不同地方就是P2146是无环的,这题是有环的. 很显然,如果有几个软件的依赖关系形成环,那么这几个软件就可以被看成是一个大软件,其价值和空间都是原先的总和. 那么,我们就可以利用Tarjan算法求强连通分量+缩点,最后加一个树上的背包就可以了. 注意,缩点后的图不一定是一棵树,但是我们可以人为的加入一个权值为零的根节点,连接所有入度为0的点. 有关Tarjan算法和强连通分量: [Luogu P3387]缩点模板(强连通分量Tarjan