题目链接
题意:
给出一个 n 个点,m 条边的无向图。
(
2
≤
n
≤
50
,
n
−
1
≤
m
≤
n
(
n
−
1
)
/
2
)
(2≤n≤50 , n−1≤m≤n(n−1)/2)
(2≤n≤50,n−1≤m≤n(n−1)/2)
每条边对应一种类
c
i
c_i
ci,长度都为 1。每一种类
t
t
t 的边有价值
w
t
w_t
wt。
从起点 1 出发,经过一条边的花费为:
k
∗
w
i
k * w_i
k∗wi,其中 k 为走过的此种类的边的次数。
对于每个点,问从起点到该点的最短路中,最小花费为多少?
分析:
一开始是这样想的:
在队列中存结构体node,node里有数组来记录走到这个点时,每种道路的经过次数。
类似于最短路计数,走到一个点,如果到该点的距离和其最短距离相同,那么比较经过道路的花费,如果花费更少的话就将花费替换,压入队列。否则就不压。
这样做问题出在哪呢?问题出在,如果最短距离相同的话,就算花费不比最短距离小,也要压入队列。因为可能后面点的最佳状态由当前点的这个非最佳状态转移过来。
但是结构体里放数组的话,出现了莫名错误,运行不出来。
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N], w[N];
vector<PII> e[N];
int f[N], dist[N], cco[N];
int cnt[N], t[N];
struct node{
int x, cost, cnt[N];
};
void bfs()
{
cout << "&&\n";
queue<node> que;
que.push({1, 0, *t});
while(que.size())
{
int x = que.front().x, cost = que.front().cost;
for(int i=1;i<=m*m;i++) cnt[i] = que.front().cnt[i];
que.pop();
for(auto it : e[x])
{
int tx = it.fi, type = it.se;
if(!f[tx]){
f[tx] = f[x] + 1;
for(int i=1;i<=m*m;i++) t[i] = cnt[i];
t[type] ++;
cco[tx] = cost + t[type]*w[type];
que.push({tx, cco[tx], *t});
}
else if(f[tx] == f[x] + 1)
{
cco[tx] = min(cco[tx], cost + (cnt[type] + 1)*w[type]);
for(int i=1;i<=m*m;i++) t[i] = cnt[i];
t[type] ++;
que.push({tx, cost + t[type]*w[type], *t});
}
}
}
}
而将数组换作map后,后面mle了。
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N], w[N];
vector<PII> e[N];
int f[N], dist[N], cco[N];
struct node{
int x, cost;
map<int, int> mp;
};
void bfs()
{
queue<node> que;
que.push({1, 0, tmp});
while(que.size())
{
int x = que.front().x, cost = que.front().cost;
map<int, int> mp = que.front().mp;
que.pop();
for(auto it : e[x])
{
int tx = it.fi, type = it.se;
if(!f[tx]){
f[tx] = f[x] + 1;
tmp = mp;
tmp[type] ++;
cco[tx] = cost + tmp[type]*w[type];
que.push({tx, cco[tx], tmp});
}
else if(f[tx] == f[x] + 1)
{
cco[tx] = min(cco[tx], cost + (mp[type] + 1)*w[type]);
tmp = mp;
tmp[type] ++;
que.push({tx, cost + tmp[type]*w[type], tmp});
}
}
}
}
正解是这样的:
可以先 bfs 一遍求出所有点的最短距离。
然后从起点开始 dfs,按照到每个点的最短路径走,同时记录走过的边的种类。
每个点的最小花费为其所有最短路径中最小花费的最小值。
Code:
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define mem(a,b) memset(a,b,sizeof a)
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'
map<int,int> mp;
/**/
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N], dist[N], w[N];
vector<PII> e[N];
int sum, cnt[N], f[N];
int ans[N];
void bfs()
{
queue<int> que;
que.push(1);
dist[1] = 1;
while(que.size())
{
int x = que.front();
que.pop();
for(auto it:e[x])
{
int tx = it.fi, type = it.se;
if(dist[tx]) continue;
dist[tx] = dist[x] + 1;
que.push(tx);
}
}
}
void dfs(int x)
{
for(auto it : e[x])
{
int tx = it.fi, type = it.se;
if(dist[tx] != dist[x] + 1) continue;
// if(sum + (cnt[type] + 1) * w[type] > ans[tx]) continue; //注意这里就算不是最小花费也要搜下去,因为下面点的最小花费可能是由这个状态转移过去
ans[tx] = min(ans[tx], sum + (cnt[type] + 1) * w[type]);
cnt[type] ++;
sum += cnt[type] * w[type];
dfs(tx);
sum -= cnt[type] * w[type];
cnt[type] --;
}
}
signed main(){
Ios;
cin >> n >> m;
for(int i=1;i<=m;i++) cin>>w[i];
while(m--)
{
int x, y, z;
cin >> x >> y >> z;
e[x].pb({y, z});
e[y].pb({x, z});
}
bfs();
for(int i=1;i<=n;i++) ans[i] = 1e9;
dfs(1);
for(int i=2;i<=n;i++) cout << ans[i] << "\n";
return 0;
}
经验:
一看到这道题的时候就发现数据范围有猫腻,为什么点数这么少?
然而看了题解之后才发现,可以dfs。
此外需要注意的是,这道题和记忆化搜索不同,如果搜到一个点不是最小花费,还需要继续往下走,因为下面点的最佳状态可能由当前点的非最佳状态转移过去。
而普通的最短路只有一个状态:距离,这个状态会递增的,所以如果到一个点不如其之前的最佳状态就不用往下走了;
而此题的状态并不满足递增,状态数组之间没有之间关联。所以不能记忆化。