洛谷 P13823

初步转化

我们将 GGG 中的边改为无向边得到无向图 G′G'G,并在存双向边的时候将与原边朝向一样的那条边的权值记为 111,另一条边权记为 000

我们先定义一条在 G′G'G 中的路径 u1→u2…uk−1→uku_1 \rightarrow u_2\dots u_{k-1} \rightarrow u_ku1u2uk1uk 的“变化次数”为 ∑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=1k2 c(e(ui,ui+1))  c(e(ui+1,ui+2)),从 u1u_1u1uku_kuk 的路径中变化次数最短的那一条为 u1u_1u1uku_kuk 的“变化次数最短路”。

我们需要求每个点 uuuG′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;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值