题解 P5201 【[USACO19JAN]Shortcut G】

刚开始拿到这题的时候我直接懵逼了:字典序是啥玩意,这东西貌似很高大尚的亚子

然而,认真读了一下题后,我发现其实这道题跟我做过的某些题很像

比如: timeline

可能大佬们会说,貌似这题跟timeline没有一毛钱关系啊...

其实,这道题可以理解为,算出每个点到1的最短距离,然后计算每个点对点1连线最大能减少的距离

那么问题来了,你怎么知道这个点跟1连线后有几头牛改变了路径?

这里就得到了这题的核心思想:我们发现,奶牛只有在走原先路径时遇到捷径才会去.他们不会刻意走去捷径.

于是,这题就可以转化为,求出(这个点到点1的距离-连边的距离)*经过这点的奶牛的数量的和

说人话

求出每个点的子树的数量*从这个节点用捷径能节省的距离

这样会好理解点吗?

好下面正式开始解题:

1.求最短距离

模板题,从点1用 (dijkstra) 求最短距离,这个不详细讲

2.求子树

敲重点:
这里就到了跟timeline相似的地方了.我们发现,如果一个点的子树还没有处理完,那么我们无法用这个点去更新他的父节点(如果无法理解可以看我的timeline的解法(timeline题解).

所以,对于每个节点,我们将其父节点设为不可更新.然后,我们将所有可以更新的节点放进 (queue) 里. 每当一个子节点要更新父节点的时候,我们将父节点的子叶数量-1.当一个父节点没有子叶了,那么他将会转化为一个子节点,然后我们就可以拿它继续更新他的父节点了

for (pp v : adj[qf]){
      if (dist[v.f]==dist[qf]-v.s){
        constrain[v.f]--;//父节点子叶--
        pos[v.f]+=pos[qf];
        if (constrain[v.f]==0) q.push(v.f);//如果父节点没有子叶了
        break;
      }
    }

3.答案

答案之前其实已经提到过,就是每个点的子叶数量*用捷径所能减少的距离

for (long long i=n;i>=1;i--){
  ans = max(ans,pos[i]*(dist[i]-k));
}

4.字典序的求法

这里是很多链式前向星的大佬卡住的地方.正常的方法是用fa存.然而,我们发现,如果用vector存图,我们可以将去的点排序,然后将遇到的第一个父节点(也就是距离最短的点)扔进队列.

这里我写的spfa没写dij,但是想法是一样的

又到了具体代码的时间了:

#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <cstring>
using namespace std;
const long long MAXN = 1e5+5;
#define pp pair<long long,long long>
#define f first
#define s second
long long dist[MAXN], n,m,k, pos[MAXN],constrain[MAXN], ans= 0 ;
vector<pp> adj[MAXN];
bool inq[MAXN],vis[MAXN];
inline void spfa(long long source){
  memset(dist,0x3f3f3f3f,sizeof(dist));
  dist[source] = 0;
  queue<long long> q;
  q.push(source);
  while(!q.empty()){
    long long qf = q.front();q.pop();
    inq[qf] = false;
    for (pp v : adj[qf]){
      if (dist[v.f]>dist[qf]+v.s){
        dist[v.f] = dist[qf]+v.s;
        if (!inq[v.f]) inq[v.f] = true,q.push(v.f);
      }
    }
  }
}//板子,不解释
inline void dfs(long long source){
  for (pp v : adj[source]){
    if (dist[v.f]==dist[source]-v.s){//如果某个点到点1的距离-他去父节点的距离==父节点到点1的距离
      constrain[v.f]++;
      break;//已经改过,break能保证字典序最小
    }
  }
}
inline void bfs(){
  queue<int> q;
  for (int i=1;i<=n;i++) if (!constrain[i]) q.push(i);//如果是子叶
  while(!q.empty()){
    int qf = q.front();q.pop();
    for (pp v : adj[qf]){
      if (dist[v.f]==dist[qf]-v.s){//同上
        constrain[v.f]--;//子叶-1
        pos[v.f]+=pos[qf];//子叶数量相加
        if (constrain[v.f]==0) q.push(v.f);//如果父节点变成了子叶
        break;//字典序
      }
    }
  }
}
long long a,b,c;
int main(){
  cin >> n >> m >> k;
  for (long long i=1;i<=n;i++) cin >> pos[i];
  for (long long i=0;i<m;i++){
    cin >> a >> b >> c;
    adj[a].push_back(make_pair(b,c));
    adj[b].push_back(make_pair(a,c));
    //vector的建边方式
  }
  for (long long i=1;i<=n;i++) sort(adj[i].begin(),adj[i].end());//排序保证字典序
  spfa(1);//板子
  for (long long i=1;i<=n;i++) dfs(i);//这名字...其实不是dfs
  bfs();
  for (long long i=n;i>=1;i--) ans = max(ans,pos[i]*(dist[i]-k));//求答案
  cout << ans;
}
原文地址:https://www.cnblogs.com/DannyXu/p/12490411.html