题解:P13823 「Diligent-OI R2 C」所谓伊人
P13823 「Diligent-OI R2 C」所谓伊人
题意
给一个有向图,点有点权。
当一个点可达另一点时,两点可交换。
在点权最大前提下,最小化交换次数。
思路
先想点权什么时候最大,显然只要两点间有边相连,无论方向,这两点可交换。
此时,只要将原有向图改成无向图,就可以确定每个联通块内的点权最大值是一样的。
然而,怎样最小化交换次数呢?
1 -> 2 -> 3 1,3交换 ans=1
1 -> 2 <- 3 1,3交换 ans=2
上面 1 点到 3 点间方向改变了一次,答案的贡献就多一。因此要记录方向的改变次数。
如此一来,建一个双层图即可,一个正图,一个反图。
改变方向相当于从一个图走到另一个图。
实现
判联通块,\(O(n)\) 扫一遍全图。
算贡献时每个块跑一遍 SPFA(巨佬说这叫 01 BFS 蒟蒻不懂)。由于边权只有 \(0\) 和 \(1\),基本退化成 BFS,\(O(n+m)\)。
细节
每个块内源点(最大点)可能不止一个,本人用 vector 存。
特判自己与自己交换,贡献为 \(0\),所以跑完后答案改为 \(-1\)。(最后统一加了一)
CODE
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e6+5;
int n,m;
int a[N];
vector<pair<int,int> > g[N];
int fa[N],tot;
int mx[N];
vector<int> p[N];
int dis[N];
void dfs(int rt){
fa[rt]=tot;
if(mx[tot]<=a[rt]&&rt<=n){
if(mx[tot]<a[rt]){
mx[tot]=a[rt];
p[tot].clear();
p[tot].push_back(rt);
}
else{
p[tot].push_back(rt);
}
}
for(int i=0;i<g[rt].size();i++){
if(fa[g[rt][i].first]) continue;
dfs(g[rt][i].first);
}
return;
}
queue<int> q;
void spfa(int rt){
while(!q.empty()) q.pop();
q.push(rt);
q.push(rt+n);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=0;i<g[u].size();i++){
int v=g[u][i].first;
if(dis[u]+g[u][i].second<dis[v]){
dis[v]=dis[u]+g[u][i].second;
q.push(v);
}
}
}
dis[rt]=-1;
dis[rt+n]=-1;
return;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
g[u].push_back(make_pair(v,0));
g[v+n].push_back(make_pair(u+n,0));
}
for(int i=1;i<=n;i++){
g[i].push_back(make_pair(i+n,1));
g[i+n].push_back(make_pair(i,1));
}
for(int i=1;i<=n;i++){
if(fa[i]) continue;
tot++;
dfs(i);
}
memset(dis,0x3f,sizeof(dis));
for(int i=1;i<=tot;i++)
for(int j=0;j<p[i].size();j++)
dis[p[i][j]]=dis[p[i][j]+n]=0;
for(int i=1;i<=tot;i++)
for(int j=0;j<p[i].size();j++)
spfa(p[i][j]);
for(int i=1;i<=n;i++)
cout<<min(dis[i],dis[i+n])+1<<' ';
return 0;
}
题外话
出题人魔怔了,为了出题不惜放出头图!!!证据如下:
这里本来有一个 Klg 进行 mega dunk 的头图,但是出题人觉得过于【数据删除】于是撤掉了。
完结撒花!!!

浙公网安备 33010602011771号