AtCoder Beginner Contest 478
E lt and le
前置知识:Kosaraju
题目大意
给定长度为
- 若
:要求 ; - 若
:要求 。
请判定是否存在满足全部 Yes 并构造任意一组满足条件的解;若不存在,输出 No。
数据范围:
- 所有输入均为整数。
思路
本题可抽象为一类差分约束系统与有向图最长路问题。
- 约束转化与图论建模:
- 对于
的非严格不等式 ,等价于 ,可从 向 连一条有向边,权值为 。 - 对于
的严格不等式 ,在整数域下等价于 ,可从 向 连一条有向边,权值为 。
- 强连通分量 (SCC) 缩点:
- 若图中存在一个有向环,环上所有顶点满足
,则环上全部节点的取值必须严格相等。 - 若环内存在至少一条权值为
的严格小于边,则会推导出 的矛盾,此时判定无解,直接输出 No。 - 因此,可以通过 Kosaraju 或 Tarjan 算法求出原图的所有强连通分量(SCC)。同一分量内部所有顶点的取值必须相同,且同一分量内部不允许存在权值为
的自环或环边。
- 利用 Kosaraju 编号特性的 DAG 最长路 DP:
- 缩点后原图转化为有向无环图(DAG)。
- 拓扑序性质:Kosaraju 算法在反图上第二次遍历时,按照原图后序遍历数组的逆序分配连通块。若存在分量间的前向边
,在原图 DFS 中 必然先于 结束遍历,因此逆序处理时 必然先于 被访问。换言之,分配得到的 SCC 编号 天然构成了缩点后 DAG 的一个有效拓扑排序。 - 设
表示分量 的最小合法取值,初始时置为基准下界 。 - 按编号
顺序遍历各个分量,遍历所有跨分量出边 ,执行最长路松弛:
- 由于序列元素有上限限制
,遍历过程中若出现任一分量满足 ,说明最长依赖链长度已超过值域上限,判定无解;否则,令 即可得到一组满足条件的构造。
复杂度分析
时间复杂度:
- 构建原图与反图需要
时间。 - Kosaraju 算法进行两次深度优先遍历求解强连通分量的时间复杂度为
。 - 遍历所有约束边以检测环内合法性并建立分量出边图,耗时
。 - 在 DAG 上沿拓扑序进行动态规划,各点与各边被访问常数次,耗时
。 - 综合总时间复杂度为
,可在限制时间内高效通过。
空间复杂度:
- 原图、反图与缩点后 DAG 边的邻接表占用空间为
。 - 节点访问标记
vis、SCC 映射数组scc、DFS 后序记录org及 DP 数组f等均占用空间。 - 综合总空间复杂度为
。
参考代码
参考代码
cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
struct edge{
int t,u,v;
};
void fc() {
int n,q;std::cin>>n>>q;
// g: 原图邻接表, rg: 反图邻接表
std::vector<std::vector<int>>g(n+1),rg(n+1);
std::vector<int>scc(n+1);
int cnt=0;
std::vector<int>org;
std::vector<bool>vis(n+1);
std::vector<edge>e(q+1);
for(int i=0;i<q;i++){
std::cin>>e[i].t>>e[i].u>>e[i].v;
g[e[i].u].push_back(e[i].v);
rg[e[i].v].push_back(e[i].u);
}
// Kosaraju 算法第一阶段:记录后序遍历序列
auto dfs1=[&](auto&&self,int u)->void{
vis[u]=true;
for(auto& v:g[u]){
if(!vis[v])self(self,v);
}
org.push_back(u);
};
// Kosaraju 算法第二阶段:在反图上回溯标记连通块
auto dfs2=[&](auto&&self,int u,int c)->void{
scc[u]=c;
for(auto& v:rg[u]){
if(!scc[v])self(self,v,c);
}
};
for(int i=1;i<=n;i++)if(!vis[i])dfs1(dfs1,i);
// 逆序访问产生的 cnt 标号天然构成 DAG 拓扑序
for(int i=n-1;i>=0;i--)if(!scc[org[i]])dfs2(dfs2,org[i],++cnt);
// 缩点并建立新图边表
std::vector<std::vector<std::pair<int,int>>>deg(cnt+1);
for(int i=0;i<q;i++){
if(scc[e[i].u]==scc[e[i].v]){
// 同一强连通分量内出现严格小于边,则产生矛盾
if(e[i].t){std::cout<<"No";return;}
}else{
deg[scc[e[i].u]].push_back({scc[e[i].v],e[i].t});
}
}
// 拓扑序最长路 DP
std::vector<int>f(cnt+1,1);
for(int u=1;u<=cnt;u++){
if(f[u]>n){
std::cout<<"No";return;
}
for(auto& [v,t]:deg[u]){
f[v]=std::max(f[v],f[u]+t);
}
}
std::cout<<"Yes\n";
for(int i=1;i<=n;i++)std::cout<<f[scc[i]]<<" ";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t = 1;
//std::cin>>t;
while (t--) fc();
return 0;
}