1.DP拆点+最短路:https://www.acwing.com/problem/content/1133/
显然,我们只知道位置是不够的,因此我们还需要拆点:令表示走到了位置(二维投射到一维)拿了钥匙(01二进制)的最小步数。
我们先暂时不考虑图上问题,我们用刷表(DP转移的第二种形式)分析转移:
1.贪心的想,有钥匙拿就拿,不浪费步数:
2.正常走一步:
但是这里的DP关系不一定满足拓扑序,我们不妨把每一个DP抽象成空间的点,转移就是图上的走。于是问题就是求一个最短路。
同时注意到这里路的权值只有0/1,直接用双端队列即可。
这里建边也比较麻烦,我们可以这么考虑:
首先按照题目把钥匙的门连起来,然后类似BFS的我们枚举每一个格子,循环4个方向,在不出界的条件下建边即可。
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
int n,m,p;
int k;
struct node{
int dian,id;
};
int key[11101];
vector<node> edge[100010];
int g[101][101];
set<pair<int,int> > s;
int dis[1010][2000];
int st[1010][2000];
void build(){
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
for(int k=0;k<4;k++){
int x = i + dx[k], y = j + dy[k];
if (x<=0 || x > n || y<=0 || y > m) continue;
int a = g[i][j], b = g[x][y];
if (!s.count({a, b})) edge[a].push_back({b,0});
}
}
}
}
int bfs(){
memset(dis,0x3f,sizeof(dis));
dis[1][0]=0;
deque<pair<int,int> > q;
q.push_front({1,0});
while(!q.empty()){
auto ck=q.front();
q.pop_front();
if(st[ck.x][ck.y]) continue;
st[ck.x][ck.y]=1;
if (ck.x == n * m) return dis[ck.x][ck.y];
if(key[ck.x]){
int state = ck.y | key[ck.x];
if(!st[ck.x][state]&&(dis[ck.x][state] > dis[ck.x][ck.y]))
{
dis[ck.x][state] = dis[ck.x][ck.y];
q.push_front({ck.x, state});
}
}
for(int i=0;i<edge[ck.x].size();i++){
auto fk=edge[ck.x][i];
if(fk.id&&((ck.y>>(fk.id-1))&1)==0) continue;
if (dis[fk.dian][ck.y] > dis[ck.x][ck.y] + 1){
dis[fk.dian][ck.y] = dis[ck.x][ck.y] + 1;
q.push_back({fk.dian, ck.y});
}
}
}
return -1;
}
int main(){
cin>>n>>m>>p>>k;
for(int i=1,t=1;i<=n;i++){
for(int j=1;j<=m;j++) g[i][j]=t++;
}
for(int i=1;i<=k;i++){
int x1,y1,x2,y2,G;
cin>>x1>>y1>>x2>>y2>>G;
s.insert({g[x1][y1],g[x2][y2]});
s.insert({g[x2][y2],g[x1][y1]});
if(G==0) continue;
edge[g[x1][y1]].push_back({g[x2][y2],G});
edge[g[x2][y2]].push_back({g[x1][y1],G});
}
build();
int y;
cin>>y;
while(y--){
int x, y, c;
cin >> x >> y >> c;
key[g[x][y]] |= 1 << c - 1;
}
cout<<bfs();
}
2.最短路计数:https://www.acwing.com/problem/content/1136/
主要结论:计数顺序可以顺着BFS,迪杰斯特拉,而贝尔曼之类的需要先建立好路径转移的拓扑序树,比较麻烦。
证明就不详细展开了,最关键的就是确保第一次出了的点不会被后来的点更新。
AC代码:
#include<bits/stdc++.h>
using namespace std;
vector<int> edge[100010];
int dis[1000010];
int cnt[1000010];
int n,m;
int mod=1e5+3;
queue<int> q;
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
edge[x].push_back(y);
edge[y].push_back(x);
}
memset(dis, 0x3f, sizeof dis);
dis[1]=0;
cnt[1]=1;
q.push(1);
while(!q.empty()){
int ck=q.front();
q.pop();
for(int i=0;i<edge[ck].size();i++){
int w=edge[ck][i];
if(dis[w]>dis[ck]+1){
dis[w]=dis[ck]+1;
cnt[w]=cnt[ck];
q.push(w);
}
else if(dis[w]==dis[ck]+1){
cnt[w]=(cnt[w]+cnt[ck])%mod;
}
}
}
for(int i=1;i<=n;i++) cout<<cnt[i]<<endl;
}
3.次短路计数:https://www.acwing.com/problem/content/385/
类似的,无论是最短还是次短,优先队列弹出的一定是最终的答案,次数也不会在后面杯更新,正确性可知。
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1010, M = 20010;
struct Ver
{
int id, type, dist;
bool operator> (const Ver &W) const
{
return dist > W.dist;
}
};
int n, m, S, T;
int h[N], e[M], w[M], ne[M], idx;
int dist[N][2], cnt[N][2];
bool st[N][2];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
int dijkstra()
{
memset(st, 0, sizeof st);
memset(dist, 0x3f, sizeof dist);
memset(cnt, 0, sizeof cnt);
dist[S][0] = 0, cnt[S][0] = 1;
priority_queue<Ver, vector<Ver>, greater<Ver>> heap;
heap.push({S, 0, 0});
while (heap.size())
{
Ver t = heap.top();
heap.pop();
int ver = t.id, type = t.type, distance = t.dist, count = cnt[ver][type];
if (st[ver][type]) continue;
st[ver][type] = true;
for (int i = h[ver]; ~i; i = ne[i])
{
int j = e[i];
if (dist[j][0] > distance + w[i])
{
dist[j][1] = dist[j][0], cnt[j][1] = cnt[j][0];
heap.push({j, 1, dist[j][1]});
dist[j][0] = distance + w[i], cnt[j][0] = count;
heap.push({j, 0, dist[j][0]});
}
else if (dist[j][0] == distance + w[i]) cnt[j][0] += count;
else if (dist[j][1] > distance + w[i])
{
dist[j][1] = distance + w[i], cnt[j][1] = count;
heap.push({j, 1, dist[j][1]});
}
else if (dist[j][1] == distance + w[i]) cnt[j][1] += count;
}
}
int res = cnt[T][0];
if (dist[T][0] + 1 == dist[T][1]) res += cnt[T][1];
return res;
}
int main()
{
int cases;
scanf("%d", &cases);
while (cases -- )
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
idx = 0;
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
scanf("%d%d", &S, &T);
printf("%d\n", dijkstra());
}
}