[无聊测试赛] T8 佳佳的魔法药水

思路比较新奇,但是想到了题目还是挺好做的

看到最小和次数可以想到最短路.看到数据范围可以想到必须在跑dij的时候记录次数.由于要记录的是最短路的方案,易证如果一种药有一种更便宜的配置方法,我们不会记录贵的那种.

于是,我们可以将这道题转化为一个用堆优化的dij.如果一种药水在堆顶,那么这种药水不可能有更便宜的配置方法

如果由A药水和B药水合成C药水比之前找到的配置方法便宜,那么C药水的配置方法数量为 A药水的配置方法 (*) B药水的配置方法.

如果由A药水和B药水合成C药水等于之前找到的配置方法,那么C药水的配置方法数量为 C药水的配置数量 (+) A药水的配置方法 (*) B药水的配置方法.

在遍历图的时候,只有两种药水都被证实为最小花费,他们合成的药水才会是他们能得到的最小值.要证明这两种的最小花费的方法是:如果他俩在堆里出现过,那么他们一定是最小花费.这个操作可以用vis数组实现

遍历整张图,最后到0的距离+配置0的方案数就是答案

#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
#define pp pair<int,int>
#define f first
#define s second
const int MAXN = 1e3+5;
vector<pp> adj[MAXN];
priority_queue<pp> q;
int n;
int dist[MAXN], ways[MAXN],ans1,ans2,in[MAXN];
bool vis[MAXN];
int main(){
  cin >> n;
  for (int i=0;i<n;i++){
    cin >> dist[i];
    ways[i] = 1;//一开始都只有一种
    q.push(make_pair(-dist[i],i));//扔进堆
  }
  ans1 = dist[0]; ans2 = 1;
  int a,b,c;
  while(cin >> a >> b >> c){
    adj[a].push_back(make_pair(b,c));
    if(a!=b)adj[b].push_back(make_pair(a,c));//注意,他可能出现A+A=C.这种情况不需要连两次边
  }
  while(!q.empty()){
    int qf = -q.top().f, qs = q.top().s; q.pop();
    if (dist[qs]!=qf) continue;//如果一个点的最小距离不等于在堆顶的距离,说明这个点已经更新过了,不需要再更新
    vis[qs] = true;//这个点被拿过了
    for (pp v : adj[qs]){
      if (!vis[v.f]) continue;//如果另一种药水没被拿过,我们不能确定现在他的距离是最小的
      if (dist[v.s]>qf+dist[v.f]){
        dist[v.s] = qf+dist[v.f];
        ways[v.s] = ways[v.f]*ways[qs];
        q.push(make_pair(-dist[v.s],v.s));//刚刚讲过的转移
      }else if (dist[v.s]==qf+dist[v.f]){
        ways[v.s] += ways[v.f]*ways[qs];
      }
    }
  }
  cout << dist[0] << " " <<ways[0];//答案
}
原文地址:https://www.cnblogs.com/DannyXu/p/12536356.html