[无聊测试赛] 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];//答案
}