一本通蓝皮题解
最小生成树
1486:【例题1】黑暗城堡
求最短路径生成树的个数
先求出根节点到各点的最短路径
然后统计每个点的答案个数
如果一个节点到1号节点的最短路 = 另一个和它有连边的节点到根节点的最短路 + 它们两个节点之间的直接距离 这个点的个数++
最后用乘法原理统计答案 将每个点的方案数乘起来
1487:【例 2】北极通讯网络
求最小生成树中第n-k大的边
正常做kruskal 到cnt>=n-k时停止 输出答案
注意建图时细节
最短路问题
1494:【例 1】Sightseeing Trip
求无向图的最小环+输出路径
求最小环方法
利用floyd
首先假设最小环是u-k-v-u
任意删去一条边u-v
则u-k-v一定是u到v的最短路径
我们知道在Floyd算法枚举k的时候
已经得到了前 k-1 个点的最短路径
这 k-1 个点不包括点 k
并且他们的最短路径中也不包括 k 点
所以我们选出i j
f(i,j)一定为i到j不经过k的最短路径
加上k为中间点 就有可能是最小环
输出路径方法
1.存中间点
#include<bits/stdc++.h>
using namespace std;
#define N 110
#define int long long
int n, m;
int f[N][N], ans, u, v, w, d[N][N], p[N][N];
#define inf 1e13;
void dfs(int i, int j) {
if (!p[i][j]) printf("%lld ", j);
else {
dfs(i, p[i][j]);
dfs(p[i][j], j);
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
ans=inf;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(i!=j) d[i][j]=f[i][j]=inf;
}
}
for (int i = 1; i <= m; ++i)
{
cin >> u >> v >> w;
d[u][v] = d[v][u] = min(d[u][v], w);
f[u][v] = f[v][u] = min(f[u][v], w);
}
for (int k = 1; k <= n; ++k) {
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
if (i == j || i == k || j == k) continue;
ans = min(ans, d[i][k] + d[k][j] + f[i][j]);
}
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
}
if (ans >= 1e13) {
cout<<"No solution.";
return 0;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
f[i][j]=d[i][j];
}
}
for (int k = 1; k <= n; ++k) {
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
if (i == j || i == k || j == k) continue;
if (d[i][k] + d[k][j] + f[i][j] == ans) {
printf("%lld ", i);
dfs(i, j);
printf("%lld",k);
return 0;
}
}
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
if (f[i][k] + f[k][j] < f[i][j]) {
f[i][j] = f[i][k] + f[k][j];
p[i][j] = k;
}
}
return 0;
}
2.存后继/前驱
#include<bits/stdc++.h>
using namespace std;
const int maxn=100;
int n;
int a[maxn][maxn];
int f[maxn][maxn];
int main() {
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
f[i][j]=j;
}
}
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
a[i][j] = min(a[i][j], a[i][k] + a[k][j]);
if(a[i][j]>a[i][k]+a[k][j])
{
a[i][j]=a[i][k]+a[k][j];
f[i][j]=f[i][k];
}
}
}
}
int s,t;
cin>>s>>t;
cout<<a[s][t]<<endl;
cout<<s<<' ';
while(f[s][t]!=t)
{
cout<<f[s][t]<<' ';
s=f[s][t];
}
cout<<t;
return 0;
}
1495:【例 2】孤岛营救问题
能走四个方向 每两个点之间可能有某种类型的门/墙
每个点里可以获得某种类型的钥匙
墙不能走 门可以用钥匙开
求最少步数
BFS+状压记录钥匙状态
结合代码食用
#include <bits/stdc++.h>
using namespace std;
const int N=12;
const int dx[]={1,-1,0,0},dy[]={0,0,1,-1};
int n,m,e[N][N][N][N];//存墙或门
int cnt[N][N];//存点的钥匙数
int key[N][N][N];//存点的钥匙是开哪种门 key[x][y][a]表示点x,y的第a个钥匙开哪种门
bool vis[N][N][1<<14];
struct node {
int x,y,k,d;//x y 表示当前坐标 k表示钥匙的状态 d表示步数
node() {x=y=k=d=0;}
node(int _x,int _y,int _k,int _d)
{
x=_x,y=_y,k=_k,d=_d;
}
};
int getkey(int x,int y) //获取点的钥匙
{
int ans=0;
for(int i=1;i<=cnt[x][y];++i) ans|=(1<<(key[x][y][i]-1));
return ans;
}
int bfs(int sx,int sy) {
queue<node> q;
int sk=getkey(sx,sy);
q.push(node(sx,sy,sk,0));
vis[sx][sy][sk]=1;
while(!q.empty()) {
node u=q.front(); q.pop();
if(u.x==n&&u.y==m) return u.d;//到终点
int ux=u.x,uy=u.y;
for(int i=0;i<4;++i) {
int vx=ux+dx[i],vy=uy+dy[i],opt=e[ux][uy][vx][vy];
if( vx<1||vx>n||vy<1||vy>m || opt<0 ||(opt&& !(u.k&(1<<(opt-1))) )) continue;
int nxt=u.k|getkey(vx,vy);
if(vis[vx][vy][nxt]) continue;
q.push(node(vx,vy,nxt,u.d+1)),vis[vx][vy][nxt]=1;
}
}
return -1;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int k,s,p;
cin>>n>>m>>p;
cin>>k;
for(int i=1;i<=k;i++)
{
int x1,y1,x2,y2,g;
cin>>x1>>y1>>x2>>y2>>g;
if(g) e[x1][y1][x2][y2]=e[x2][y2][x1][y1]=g;
else e[x1][y1][x2][y2]=e[x2][y2][x1][y1]=-1;
}
cin>>s;
for(int i=1;i<=s;i++)
{
int x,y,q;
cin>>x>>y>>q;
key[x][y][++cnt[x][y]]=q;
}
printf("%d\n",bfs(1,1));
return 0;
}
1496:【例 3】架设电话线
求出一条从起点到终点的路径,使路径上第K+1大的边权尽量小。
二分+最短路
求...大的尽量小 考虑二分答案
将大于当前答案的边权设为1 小于等于设为0
跑最短路
如果结果大于k 证明答案小了
反之答案大了