Linking
题意:
给出一个 n 个点 m 条边的无向图,起点为 1,终点为 n。
其中有 k 个特殊的点,现在要加一条边连接两个特殊的点,使得从 1 到 n 的最短路最长。
输出这个最短路长度。
思路:
容易想到其朴素做法,两重循环找出两个点 ,使得起点到一点的最短距离 + 终点到另一点的最短距离 + 1 最大。因为是最短路,所以这个最大值要和原来1到n的最短距离取min。
假设找出的两个点为 i 和 j,起点到两个点的距离为xi, xj,终点到两个点的距离为yi, yj,那么加这一条边之后的最短距离为:
m
i
n
(
x
i
,
x
j
)
+
m
i
n
(
y
i
,
y
j
)
+
1
min(x_i, x_j) + min(y_i, y_j) + 1
min(xi,xj)+min(yi,yj)+1.
假设
x
i
+
y
j
≤
x
j
+
y
i
x_i + y_j ≤ x_j + y_i
xi+yj≤xj+yi,那么最短距离就为
x
i
+
y
j
+
1
x_i+y_j+1
xi+yj+1。后面这个式子是比较简洁的。
为了后面式子成立,要满足前面的式子,也就是要满足
x
i
−
y
i
≤
x
j
−
y
j
x_i-y_i ≤ x_j-y_j
xi−yi≤xj−yj。
所以我们可以按照
x
i
−
y
i
x_i - y_i
xi−yi 的大小对 pair(xi,yi) 从小到大排序,那么对于 i < j,总是满足
x
i
+
y
j
≤
x
j
+
y
i
x_i + y_j ≤ x_j + y_i
xi+yj≤xj+yi,那么就可以用前面位置的
x
i
x_i
xi 加上后面位置的
y
j
y_j
yj 一直取最值。
这样就可以从后往前走记录
y
j
y_j
yj 的最值往前走用当前
x
i
x_i
xi 更新最大值,O(n)。
Code:
map<int,int> mp;
const int N = 500010, mod = 1e9+7;
int T, n, m, k;
int a[N];
int e[N], ne[N], h[N], idx;
int stdis[N], endis[N], f[N];
PII p[N];
void add(int x, int y){
e[idx]=y, ne[idx]=h[x], h[x]=idx++;
}
void dij(int st, int dist[])
{
mem(f,0);
for(int i=1;i<=n;i++) dist[i]=0x3f3f3f3f;
priority_queue<PII, vector<PII>, greater<PII>> que;
que.push({0, st});
dist[st]=0;
while(que.size())
{
int x=que.top().se;
que.pop();
if(f[x]) continue;
f[x]=1;
for(int i=h[x];i!=-1;i=ne[i])
{
int tx=e[i];
if(dist[tx] > dist[x]+1)
dist[tx] = dist[x]+1, que.push({dist[tx], tx});
}
}
}
bool cmp(PII x, PII y){
return x.fi-x.se < y.fi-y.se;
}
signed main(){
Ios;
cin>>n>>m>>k;
int cnt=0;
for(int i=1;i<=k;i++){
int x;cin>>x;
mp[x]=1;
}
mem(h,-1);
while(m--)
{
int x, y;cin>>x>>y;
add(x, y), add(y, x);
}
dij(1, stdis);
dij(n, endis);
for(int i=1;i<=n;i++){
if(!mp[i]) continue;
p[++cnt]={stdis[i], endis[i]};
}
sort(p+1, p+cnt+1, cmp);
int maxa=0, ans=0;
for(int i=cnt;i>=1;i--)
{
if(i!=cnt) ans = max(ans, p[i].fi + maxa);
maxa = max(maxa, p[i].se);
}
cout<<min(ans+1, stdis[n]);
return 0;
}
经验:
最后用到了一个贪心的排序,排过序之后就可以使原来较为复杂的式子简单化。
很好的一道题!