传送门:1900E
标签:图论
题目大意
你有一个带有
n
n
n 个顶点和
m
m
m 条边的有向图
G
G
G。最初,图
H
H
H 与图
G
G
G 相同。然后你决定执行以下操作:如果存在图
H
H
H 中的顶点三元组
a
,
b
,
c
a, b, c
a,b,c,使得有一条从
a
a
a 到
b
b
b 的边和一条从
b
b
b 到
c
c
c 的边,但没有从
a
a
a 到
c
c
c 的边,则添加一条从
a
a
a 到
c
c
c 的边。
反复执行以上步骤,直到不存在这样的三元组为止。注意,执行这些操作后,
H
H
H 中的边数最多可达
n
2
n^2
n2。你还给图
H
H
H 的顶点写了一些值。具体来说,第
i
i
i 个顶点上有值
a
i
a_i
ai 写在上面。考虑由
k
k
k 个不同的顶点组成的简单路径,索引分别为
v
1
,
v
2
,
…
,
v
k
v_1, v_2, \ldots, v_k
v1,v2,…,vk。该路径的长度为
k
k
k。该路径的价值定义为
∑
i
=
1
k
a
v
i
\sum_{i=1}^k a_{v_i}
∑i=1kavi。一个简单路径被认为是最大的,如果图中没有其他具有更大长度的简单路径。在
H
H
H 中的所有最长大于或等于
k
k
k 的简单路径中,找到价值最小的那个。
输入:每个测试包含多个测试用例。第一行包含整数 t t t( 1 ≤ t ≤ 1 0 4 1≤t≤10^4 1≤t≤104),表示测试用例的数量。接下来是测试用例的描述。每个测试用例的第一行包含两个整数 n n n 和 m m m( 1 ≤ n , m ≤ 2 ⋅ 1 0 5 1≤n,m≤2⋅10^5 1≤n,m≤2⋅105),表示顶点数和边数。第二行包含 n n n 个整数 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,…,an( 0 ≤ a i ≤ 1 0 9 0≤a_i≤10^9 0≤ai≤109),表示写在图 H H H 上的顶点值。接下来的 m m m 行中的每行包含两个整数 v i v_i vi 和 u i u_i ui( 1 ≤ v i , u i ≤ n 1≤v_i,u_i≤n 1≤vi,ui≤n),表示图 G G G 中从顶点 v i v_i vi 到顶点 u i u_i ui 的边。注意,边是有方向的。还要注意,图可能有自环和多条边。保证了所有测试用例的 n n n 和 m m m 总和不超过 2 ⋅ 1 0 5 2⋅10^5 2⋅105。
输出:对于每个测试用例,输出两个数字 — H H H 中最长大于或等于 k k k 的简单路径的长度及其最小可能价值。
算法分析
主要观察点在于
H
H
H 的样子。
G
G
G 中的所有强连通分量 (SCC) 在
H
H
H 中将成为完全图。其次,取任何两个顶点
a
a
a 和
b
b
b,它们不在同一个 SCC 中。我们可以让
S
a
S_a
Sa 是与
a
a
a 同在一个 SCC 的顶点集(包括
a
a
a)。同样,
S
b
S_b
Sb 是与
b
b
b 同在一个 SCC 的顶点集。如果有从
a
a
a 到
b
b
b 的边,那么对于任意属于
S
a
S_a
Sa 的顶点
x
x
x 和属于
S
b
S_b
Sb 的顶点
y
y
y,都有从
x
x
x 到
y
y
y 的边。关于图的这两个事实都可以通过归纳证明。
现在,假设存在一条经过至少一个 SCC 的最长路径。那么这条路径将经过所有顶点,因为 SCC 中的所有顶点都连接到相同的外部顶点,并且因为 SCC 是一个完整的子图。
现在我们可以构造
H
′
H'
H′ 图。
H
H
H 中的每个 SCC 都将是
H
′
H'
H′ 中的一个顶点。顶点上的数字等于构成该 SCC 的顶点之和。如果它们之间的原始 SCC 之间有边,则会在两个新顶点之间添加边。边的权重等于进入的 SCC 的大小。还会添加一个位于索引 0 的额外顶点,并在其与其他所有顶点之间创建边。权重将根据进入的顶点的 SCC 大小确定。
由于之前的观察,
H
′
H'
H′ 的答案与
H
H
H 相同。但是请注意,
H
′
H'
H′ 是 DAG。这意味着可以使用 DP 计算其答案。
总时间和内存复杂度为
O
(
n
)
O(n)
O(n)。
代码实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10;
int val[N];
vector<int> a[N],g[N];
int dfn[N],low[N],cnt;
bool vis[N];
stack<int> st;
int sum[N],siz[N],tot;
int bl[N];
void del()
{
siz[tot]++;
vis[st.top()]=0;
sum[tot]+=val[st.top()];
bl[st.top()]=tot;
st.pop();
}
void Tarjan(int x)
{
dfn[x]=low[x]=++cnt;
vis[x]=1;
st.push(x);
for(int to:a[x])
{
if(!dfn[to])
{
Tarjan(to);
low[x]=min(low[x],low[to]);
}
else if(vis[to])
low[x]=min(low[x],dfn[to]);
}
if(low[x]==dfn[x])
{
tot++;
while(st.top()!=x)
del();
del();
}
}
int in[N];
int len[N],f[N];
int t;
void run(int o)
{
int n,m;
cin>>n>>m;
tot=cnt=0;
for(int i=1;i<=n;i++)
{
dfn[i]=low[i]=in[i]=len[i]=f[i]=val[i]=vis[i]=sum[i]=siz[i]=bl[i]=0;
a[i].clear(),g[i].clear();
}
for(int i=1;i<=n;i++)
cin>>val[i];
for(int i=1;i<=m;i++)
{
int x,y;
cin>>x>>y;
a[x].push_back(y);
}
for(int i=1;i<=n;i++)
if(!dfn[i])
Tarjan(i);
for(int i=1;i<=n;i++)
for(int to:a[i])
if(bl[to]!=bl[i])
{
g[bl[to]].push_back(bl[i]);
in[bl[i]]++;
}
queue<int> q;
for(int i=1;i<=tot;i++)
if(!in[i])
q.push(i);
int ans1=0,ans2=0;
while(q.size())
{
int x=q.front();
q.pop();
len[x]+=siz[x];
f[x]+=sum[x];
if(ans1<len[x])
ans1=len[x],ans2=f[x];
else if(ans1==len[x]&&ans2>f[x])
ans2=f[x];
for(int to:g[x])
{
in[to]--;
if(len[to]<len[x])
len[to]=len[x],f[to]=f[x];
else if(len[to]==len[x]&&f[to]>f[x])
f[to]=f[x];
if(!in[to])
q.push(to);
}
}
cout<<ans1<<' '<<ans2<<endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>t;
for(int i=1;i<=t;i++)
run(i);
return 0;
}