一本通蓝皮题解

最小生成树

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 证明答案小了
反之答案大了

posted @ 2024-05-09 13:45  zysssss  阅读(16)  评论(0编辑  收藏  举报