初步转化
我们将 GGG 中的边改为无向边得到无向图 G′G'G′,并在存双向边的时候将与原边朝向一样的那条边的权值记为 111,另一条边权记为 000。
我们先定义一条在 G′G'G′ 中的路径 u1→u2…uk−1→uku_1 \rightarrow u_2\dots u_{k-1} \rightarrow u_ku1→u2…uk−1→uk 的“变化次数”为 ∑i=1k−2 c(e(ui,ui+1)) ⊕ c(e(ui+1,ui+2))\sum_{i=1}^{k-2}\ c(e(u_i,u_i+1))\ \oplus\ c(e(u_{i+1},u_{i+2}))∑i=1k−2 c(e(ui,ui+1)) ⊕ c(e(ui+1,ui+2)),从 u1u_1u1 到 uku_kuk 的路径中变化次数最短的那一条为 u1u_1u1 到 uku_kuk 的“变化次数最短路”。
我们需要求每个点 uuu 在 G′G'G′ 中到达 uuu 所属连通块内一个点权最大的点 vvv 的“变化次数最短路” 的最小值。
进一步思考
对于每个 G′G'G′ 中的连通块,先找出每个 pup_upu 是块内最大值的点 uuu,这是好做的。然后我们想到跑一个类似于拓扑排序的东西,对于每个点记录到这个点的“变化次数最短路”的长度,以及它是走权为 111 还是 000 的边过来的(有“变化次数最短路”走权为 111 还是 000 的边过来)。
然后?没有然后了。细节搁在代码里了。复杂度比较玄学,我写的时候是为了骗分的,但是由于一条边连接的两个端点不可能互相更新好像也不会 TLE(雾)。
AC 代码
#include <bits/stdc++.h>
using namespace std;
#define rep(i,n) for(int i = 1;i <= n;i++)
#define rpt(i,a,n) for(int i = a;i <= n;++i)
#define swap(x,y) (x ^= y ^= x ^= y)
#define debug cout<<"Help!\n"
const int N = 5e5 + 5,inf = 1e9 + 7;
int p[N],ans[N],mx[N],ru[N],n,m,u,v,mxx;//ans 存每个点的“变化次数最短路”长度,mx 好像不必须,你可以在底下判断的时候换个方式就可以把它去掉啦
vector <int> g[N],t[N],rev[N],son;
bool tp[2][N],ismx[N],vis[N];
queue <int> Q;
void dfs(int u){//找连通块以及块内点权最大值
vis[u] = true,mxx = max(mxx,p[u]),son.emplace_back(u);
for(auto v : g[u]) if(!vis[v]) dfs(v);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m;
rep(i,n) cin>>p[i],ans[i] = inf;
while(m--){
cin>>u>>v;
g[u].emplace_back(v),g[v].emplace_back(u);//G'
t[u].emplace_back(v),rev[v].emplace_back(u);//正向边,反向边
}
rep(i,n){
if(vis[i]) continue;
son.clear(),mxx = 0,dfs(i);
for(auto j : son) if(p[j] == mxx) ans[j] = 0,mx[j] = p[j],Q.push(j),tp[0][j] = tp[1][j] = ismx[j] = true;//“变化次数最短路”的一个端点可以理解为上一步走正、反向边都可以(反正是0)
while(Q.size()){//拓扑排序
u = Q.front(),Q.pop();
for(auto v : t[u]){//走正向边
if(mx[v] < mx[u]){//必须走当前边,更新
tp[1][v] = true,tp[0][v] = false,mx[v] = mx[u],ans[v] = ans[u] + !tp[1][u],Q.push(v);
}
else if(mx[v] == mx[u]){//或许要走当前边
if(ans[v] > ans[u] + !tp[1][u]) tp[1][v] = true,tp[0][v] = false,mx[v] = mx[u],ans[v] = ans[u] + !tp[1][u],Q.push(v);//方案(步数)更优
else if(ans[v] == ans[u] + !tp[1][u] && !tp[1][v]) tp[1][v] = true,Q.push(v);//多一种选择
}
}
for(auto v : rev[u]){//走反向边,与正向边同理
if(mx[v] < mx[u]){
tp[0][v] = true,tp[1][v] = false,mx[v] = mx[u],ans[v] = ans[u] + !tp[0][u],Q.push(v);
}
else if(mx[v] == mx[u]){
if(ans[v] > ans[u] + !tp[0][u]) tp[0][v] = true,tp[1][v] = false,mx[v] = mx[u],ans[v] = ans[u] + !tp[0][u],Q.push(v);
else if(ans[v] == ans[u] + !tp[0][u] && !tp[0][v]) tp[0][v] = true,Q.push(v);
}
}
}
}
rep(i,n) cout<<ans[i] + !ismx[i]<<' ';//注意到不是连通快内最大值的点的答案会少算1(见拓扑排序部分)
return 0;
}
622

被折叠的 条评论
为什么被折叠?



