网络流建图/模型总结

网络流真的是一个什么强大的算法啦。令人头疼的是网络流的灵活应用之广泛,网络流的题目建图方式也是千奇百怪,所以蒟蒻打算总结一下网络流的建图方式。秉着不重复造轮子的原则(其实是博主又菜又想找个借口),网上大佬写的好的就直接贴网址了。 (更新ing)

 

大佬强无敌的总结:https://www.cnblogs.com/victorique/p/8560656.html#autoid-1-10-3 

最小割应用:https://wenku.baidu.com/view/87ecda38376baf1ffc4fad25.html 

最大权闭合子图:https://blog.youkuaiyun.com/can919/article/details/77603353

 

题目集合:https://blog.youkuaiyun.com/corsica6/article/details/88045843 

 

经典模型:

这些是上面博客大佬总结的经典模型,我也试着总结一下。

 

 

题目练习:

POJ-2987

最大权闭合子图入门题。按照上面说的连边建图跑最小割。求它的最小割,割掉后,与源点s连通的点构成最大权闭合子图,权值为(正权值之和-最小割)。所以在残余网络上从源点s开始沿着非满流边(即没割掉的边)dfs即可,遇到的就是选择的点。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
typedef long long LL;
const int N=1e4+10;
const int M=2e5+100;
const LL INF=1LL<<60;
int n,m,s,t; LL sum;

int tot=1,head[N],nxt[N<<6],ver[N<<6]; 
LL edge[N<<6];
queue<int> q;
void add_edge(int x,int y,LL z) {
    ver[++tot]=y; edge[tot]=z; nxt[tot]=head[x]; head[x]=tot;
    ver[++tot]=x; edge[tot]=0; nxt[tot]=head[y]; head[y]=tot;
}

int d[N];
bool bfs() {
    memset(d,0,sizeof(d));
    while (!q.empty()) q.pop();
    q.push(s); d[s]=1;
    while (!q.empty()) {
        int x=q.front(); q.pop();
        for (int i=head[x];i;i=nxt[i]) {
            if (edge[i] && !d[ver[i]]) {
                q.push(ver[i]);            
                d[ver[i]]=d[x]+1;
                if (ver[i]==t) return 1;
            }
        }
    }
    return 0;
}

LL dinic(int x,LL flow) {
    if (x==t) return flow;
    LL rest=flow,k;
    for (int i=head[x];i && rest;i=nxt[i]) 
        if (edge[i] && d[ver[i]]==d[x]+1){
        k=dinic(ver[i],min(rest,edge[i]));
        if (!k) d[ver[i]]=0;
        edge[i]-=k;
        edge[i^1]+=k;
        rest-=k;
    }
    return flow-rest;
}

bool vis[N];
int dfsp(int x) {
    int ret=1; vis[x]=1;
    for (int i=head[x];i;i=nxt[i]) {
        int y=ver[i];
        if (edge[i]>0 && !vis[y]) ret+=dfsp(y);
    }
    return ret;
}

int main()
{
    scanf("%d%d",&n,&m);
    s=0; t=n+1;
    for (int i=1;i<=n;i++) {
        LL x; scanf("%lld",&x);
        if (x>=0) add_edge(s,i,x),add_edge(i,s,0),sum+=x;
        if (x<0) add_edge(i,t,-x),add_edge(t,i,0);
    }
    for (int i=1;i<=m;i++) {
        int x,y; scanf("%d%d",&x,&y);
        add_edge(x,y,INF); add_edge(y,x,0);
    }
    LL flow;
    while (bfs())
        while (flow=dinic(s,INF)) sum-=flow;
    cout<<dfsp(s)-1<<" "<<sum<<endl;
    return 0;
}
View Code

 

洛谷P1345 奶牛的电信

比较明显的最小割。但是注意每台电脑只能用一次,所以要拆点限制每个点的流量为1。有一个细节:起点和终点的点不用限制流量为1(虽然题目似乎没提c1/c2不可破坏)。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=10000+10;
const int M=100000+100;
const int INF=0x3f3f3f3f;
int n,m,s,t;
struct edge{
    int nxt,to,cap;
}edges[M<<1];
int cnt=1,head[N],cur[N];

void add_edge(int x,int y,int z) {
    edges[++cnt].nxt=head[x]; edges[cnt].to=y; edges[cnt].cap=z; head[x]=cnt;
}

int dep[N]; 
queue<int> q;
bool bfs() {
    while (!q.empty()) q.pop();
    memset(dep,0,sizeof(dep));
    dep[s]=1; q.push(s);
    while (!q.empty()) {
        int x=q.front(); q.pop();
        for (int i=head[x];i;i=edges[i].nxt) {
            edge e=edges[i];
            if (!dep[e.to] && e.cap) {
                dep[e.to]=dep[x]+1;
                q.push(e.to);
            }
        }
    }
    return dep[t];
}

int dfs(int x,int lim) {
    if (x==t || !lim) return lim;
    int ret=0;
    for (int& i=cur[x];i;i=edges[i].nxt) {
        edge e=edges[i];
        if (dep[x]+1==dep[e.to] && e.cap) {
            int flow=dfs(e.to,min(lim,e.cap));
            if (flow>0) {
                edges[i].cap-=flow;
                edges[i^1].cap=+flow;
                ret+=flow; lim-=flow;
                if (!lim) break;
            }
        }
    }
    return ret;
}

int Dinic() {
    int maxflow=0;
    while (bfs()) {
        for (int i=s;i<=t;i++) cur[i]=head[i];  //当前弧优化 
        while (int flow=dfs(s,INF)) maxflow+=flow;
    }
    return maxflow;
}

int main()
{
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for (int i=1;i<=m;i++) {
        int x,y; scanf("%d%d",&x,&y);
        add_edge(x+n,y,1); add_edge(y,x+n,0);
        add_edge(y+n,x,1); add_edge(x,y+n,0);
    }    
    for (int i=1;i<=n;i++) add_edge(i,i+n,1),add_edge(i+n,i,0);
    add_edge(s,s+n,INF); add_edge(s+n,s,0);
    add_edge(t,t+n,INF); add_edge(t+n,t,0);
    add_edge(0,s,INF); add_edge(s,0,0);
    add_edge(t+n,2*n+1,INF); add_edge(2*n+1,t+n,0);
    s=0; t=2*n+1;
    cout<<Dinic()<<endl;
    return 0;
} 
View Code

 

洛谷P3159 交互棋子

 

洛谷P2825 游戏

这道题有意思也很有代表性。对于这种棋盘有互斥关系的题目都可以往网络流方面想一想。这题也是行列互斥但是注意到因为硬石头的存在导致行/列能够放多个炸弹,所以此题建图应该根据硬石头把每行/每列分成不仅仅是一行一列而是把极大没有硬石头的一串当成一行一列。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=10000+10;
const int M=100000+100;
const int INF=0x3f3f3f3f;
int n,m,s,t;
struct edge{
    int nxt,to,cap;
}edges[M<<1];
int cnt=1,head[N],cur[N];
char mp[55][55]; int r[55][55],c[55][55];

void add_edge(int x,int y,int z) {
    edges[++cnt].nxt=head[x]; edges[cnt].to=y; edges[cnt].cap=z; head[x]=cnt;
}

int dep[N]; 
queue<int> q;
bool bfs() {
    while (!q.empty()) q.pop();
    memset(dep,0,sizeof(dep));
    dep[s]=1; q.push(s);
    while (!q.empty()) {
        int x=q.front(); q.pop();
        for (int i=head[x];i;i=edges[i].nxt) {
            edge e=edges[i];
            if (!dep[e.to] && e.cap) {
                dep[e.to]=dep[x]+1;
                q.push(e.to);
            }
        }
    }
    return dep[t];
}

int dfs(int x,int lim) {
    if (x==t || !lim) return lim;
    int ret=0;
    for (int& i=cur[x];i;i=edges[i].nxt) {
        edge e=edges[i];
        if (dep[x]+1==dep[e.to] && e.cap) {
            int flow=dfs(e.to,min(lim,e.cap));
            if (flow>0) {
                edges[i].cap-=flow;
                edges[i^1].cap=+flow;
                ret+=flow; lim-=flow;
                if (!lim) break;
            }
        }
    }
    return ret;
}

int Dinic() {
    int maxflow=0;
    while (bfs()) {
        for (int i=s;i<=t;i++) cur[i]=head[i];  //当前弧优化 
        while (int flow=dfs(s,INF)) maxflow+=flow;
    }
    return maxflow;
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%s",mp[i]+1);
    int num=0;
    for (int i=1;i<=n;i++) {
        num++;
        for (int j=1;j<=m;j++) {
            if (mp[i][j-1]=='#') num++;
            r[i][j]=num;
        }
    }
    for (int j=1;j<=m;j++) {
        num++;
        for (int i=1;i<=n;i++) {
            if (mp[i-1][j]=='#') num++;
            c[i][j]=num;
        }
    }
    
    s=0; t=num+1;
    for (int i=1;i<=r[n][m];i++) add_edge(s,i,1),add_edge(i,s,0);
    for (int i=r[n][m]+1;i<=c[n][m];i++) add_edge(i,t,1),add_edge(t,i,0);
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++) 
            if (mp[i][j]=='*') {
                add_edge(r[i][j],c[i][j],INF); add_edge(c[i][j],r[i][j],0);
            }
    cout<<Dinic()<<endl;    
    return 0;
} 
View Code

 

BZOJ-4950 

题意:给出一个俯视图,每个数字代表该格子的箱子高度。我们可以从这些格子中取走一些箱子但是要使得取走后的图正视图,左视图,俯视图都不能改变。问能取走的最大箱子数。

解法:这道题没想到(是真的菜qwq)。首先容易想到每行每列的最大值不能选,然后任何不是最大值的格子就取剩一个(保证俯视图)。我们发现这样的话会有浪费,若存在某行某列的最大值相等,其实我们可以把最大值放在他们的交点处,这样能够省一个最大值,但是注意并不是只要行列相等就能省,因为一个交点最多只能代表一行一列多了就不行。那么怎么做呢?我们仔细梳理这个问题:首先还是必须行列最大值相等才能考虑省,然后注意到多个最大值相等的行列,行只能用一次,列也只能用一次,然后两个还没用过的相等行列凑到一起就能省一个最大值。哦!!这不就是二分图匹配。没错!所以我们把行当左边点,列当右边点,做二分图匹配即可。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=200+10;
 4 int n,m,mat[N];
 5 bool vis[N];
 6 vector<int> G[N];
 7 int mp[110][110],rmax[110],cmax[110];
 8 
 9 bool match(int x) {
10     for (int i=0;i<G[x].size();i++) {
11         int y=G[x][i];
12         if (!vis[y]) {
13             vis[y]=true;
14             if (mat[y]==0 || match(mat[y])) {
15                 mat[y]=x;
16                 return true;
17             }
18         }
19     }
20     return false;
21 }
22 
23 signed main()
24 {
25     scanf("%d%d",&n,&m);
26     long long ans=0;
27     for (int i=1;i<=n;i++)
28         for (int j=1;j<=m;j++) {
29             scanf("%d",&mp[i][j]);
30             rmax[i]=max(rmax[i],mp[i][j]);
31             cmax[j]=max(cmax[j],mp[i][j]);
32             if (mp[i][j]) ans+=mp[i][j]-1;
33         }
34     for (int i=1;i<=n;i++) if (rmax[i]) ans-=rmax[i]-1;
35     for (int j=1;j<=m;j++) if (cmax[j]) ans-=cmax[j]-1;
36     for (int i=1;i<=n;i++)
37         for (int j=1;j<=m;j++)
38             if (rmax[i]==cmax[j] && mp[i][j])
39                 G[i].push_back(j+n),G[j+n].push_back(i);
40     for (int i=1;i<=n;i++) {
41         memset(vis,0,sizeof(vis));
42         if (match(i)) ans+=rmax[i]-1;
43     }    
44     cout<<ans<<endl;
45     return 0;
46 } 
View Code

 

洛谷P2053

题意:m个工作人员修n辆车,不同的技术人员对不同的车进行维修所用的时间是不同的。现在需要安排这M位技术人员所维修的车及顺序,使得顾客平均等待的时间最小。

解法:感觉这道题还蛮有意思的。我们发现对于某个人如果他的修车顺序是w1->w2->w3,那么等待时间就是w1*3+w2*2+w3*1。也就是说每个人倒数第一辆修的时间是1*原时间,第二辆是2*原时间,第n辆修是n*原时间。看到数据量很小,那么我们直接暴力拆点。左边拆n个车的点,右边每个人拆成n个点(i,j)表示第i个人倒数第j辆修的点。两边分别向源汇点连容量1费用0的边,中间就要连边费用就是相应付出的时间,那么第i辆车给第j个工作人员倒数第k修的代价就是 a[i][j]*k  。连边跑费用流此题可解。

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
const int N=5000+10;
const int M=50000+10;
const int INF=0x3f3f3f3f;
int n,m,s,t,maxflow,mincost;
struct edge{
    int nxt,to,cap,cost;
}edges[M<<1];
int cnt=1,head[N],pre[N],a[66][66];

void add_edge(int x,int y,int z,int c) {
    edges[++cnt].nxt=head[x]; edges[cnt].to=y; edges[cnt].cap=z; edges[cnt].cost=c; head[x]=cnt;
}

queue<int> q;
int dis[N],lim[N]; 
bool inq[N];
bool spfa(int s,int t) {
    while (!q.empty()) q.pop();
    memset(dis,0x3f,sizeof(dis));
    memset(inq,0,sizeof(inq));
    dis[s]=0; inq[s]=1; lim[s]=INF; q.push(s);
    while (!q.empty()) {
        int x=q.front(); q.pop();
        for (int i=head[x];i;i=edges[i].nxt) {
            edge e=edges[i];
            if (e.cap && dis[x]+e.cost<dis[e.to]) {
                dis[e.to]=dis[x]+e.cost;
                pre[e.to]=i;  //即e.to这个点是从i这条边来的 
                lim[e.to]=min(lim[x],e.cap);
                if (!inq[e.to]) { q.push(e.to); inq[e.to]=1; }
            }
        }
        inq[x]=0;
    }
    return !(dis[t]==INF); 
}

void MCMF() {
    maxflow=0; mincost=0;
    while (spfa(s,t)) {
        int now=t;
        maxflow+=lim[t];
        mincost+=lim[t]*dis[t];
        while (now!=s) {
            edges[pre[now]].cap-=lim[t];
            edges[pre[now]^1].cap+=lim[t];
            now=edges[pre[now]^1].to;
        }
    }
}

int id(int x,int y) { return n+(x-1)*n+y; }

int main()
{
    scanf("%d%d",&m,&n);
    for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) scanf("%d",&a[i][j]);
    s=0; t=n+n*m+1;
    for (int i=1;i<=n;i++) add_edge(s,i,1,0),add_edge(i,s,0,0);
    for (int i=n+1;i<=n+n*m;i++) add_edge(i,t,1,0),add_edge(t,i,0,0);
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
            for (int k=1;k<=n;k++)
                add_edge(i,id(j,k),1,k*a[i][j]),add_edge(id(j,k),i,0,-k*a[i][j]);
    MCMF();
    printf("%.2lf\n",(double)mincost/n);            
    return 0;
} 
View Code

 

转载于:https://www.cnblogs.com/clno1/p/10950807.html

<think>好的,我现在需要帮助用户解决如何将ROS机器人在仿真环境中的和导航应用到实际硬件上的问题。首先,我需要回忆一下ROS的相关知识,以及硬件部署的一般流程。 用户的问题是关于实物部署的,涉及到从仿真到实体的过渡,硬件适配是关键。我需要考虑硬件接口、传感器和执行器的差异,以及可能需要的软件调整。首先,硬件适配部分,用户可能需要配置机器人的驱动,比如电机、激光雷达、摄像头等。这些在仿真中可能使用Gazebo的插件,而实际硬件需要真实的驱动程序。 然后,和导航的迁移。在仿真中,可能使用的是虚拟的传感器数据,而实际中需要处理真实传感器的噪声和校准问题。比如,激光雷达的数据可能会有更多的噪声,需要调整滤波参数。导航算法中的代价地参数也可能需要重新配置,以适应实际环境中的动态障碍和地面摩擦等因素。 网络配置也是重要的一环,ROS需要正确的网络设置才能让各个节点在不同的硬件上通信。比如,设置ROS_MASTER_URI和ROS_IP,确保所有设备能够连接到同一个ROS Master。 安全性方面,实际机器人需要紧急停止机制和碰撞检测,这在仿真中可能被忽略,但实物部署时必须考虑。可能需要添加硬件中断或软件监控节点来处理突发情况。 接下来,用户可能需要具体的步骤,比如如何迁移已有的SLAM配置到实际机器人,如何测试和调试。这里需要分步骤说明,从硬件准备到软件调整,再到测试验证。例如,先确保所有传感器驱动正常运行,然后逐步测试,再实现导航。 同时,用户可能需要了解常见的硬件适配工具,比如rosserial或ros_control,用于连接微控制器或电机控制器。这些工具可以帮助将ROS消息转换为硬件信号,反之亦然。 可能还需要提到URDF和实际机器人的差异,比如仿真中的模型可能与实际尺寸或动力学参数不同,需要调整URDF文件以匹配实际硬件。 最后,用户可能会遇到时间同步问题,尤其是使用多个传感器时,需要确保时间戳同步,可能需要使用NTP或ROS的定时机制。 总结下来,回答的结构应该是:硬件适配、传感器驱动、网络配置、参数调整、安全机制、分步实施和常见问题。需要引用相关的ROS文档或资源,比如MoveIt、Gazebo到实机的迁移指南,以及硬件接口包如ros_control的说明。</think>要将ROS机器人在仿真环境中完成的与导航迁移到实际硬件上,需要经过硬件适配、软件调整和系统集成等多个关键步骤。以下是详细流程及注意事项: --- ### 一、硬件适配与驱动配置 1. **硬件接口开发** - 为实际传感器(如激光雷达、IMU、摄像头)和电机控制器编写或适配ROS驱动。例如,使用`ros_control`框架将电机控制器与ROS连接[^1]。 - 示例代码(电机驱动节点): ```python import rospy from geometry_msgs.msg import Twist def motor_control_callback(msg): # 将Twist消息转换为实际电机控制信号 left_speed = msg.linear.x - msg.angular.z right_speed = msg.linear.x + msg.angular.z # 发送信号至硬件(如通过串口或CAN总线) rospy.init_node('motor_driver') rospy.Subscriber('/cmd_vel', Twist, motor_control_callback) rospy.spin() ``` 2. **传感器校准** - 激光雷达需校准角度偏移,摄像头需标定内参和畸变参数,IMU需校正零偏。可使用ROS工具如`camera_calibration`包[^2]。 --- ### 二、仿真到实物的参数迁移 1. **SLAM参数调整** - 实际激光雷达噪声更大,需在`gmapping`或`cartographer`中增大`sigma`参数: ```yaml # gmapping参数示例 sigma: 0.05 → 0.1 # 提高噪声容忍度 maxUrange: 4.0 → 实际传感器量程 ``` 2. **导航栈调优** - 修改`move_base`的代价地参数: ```yaml # costmap_common_params.yaml obstacle_range: 2.5 → 3.0 # 扩大障碍物检测范围 inflation_radius: 0.5 → 0.7 # 增加路径与障碍物的安全距离 ``` --- ### 三、网络与系统集成 1. **分布式通信配置** - 设置多机ROS通信(如工控机与底盘控制器): ```bash # 在机器人端 export ROS_MASTER_URI=http://主控IP:11311 export ROS_IP=本机IP ``` 2. **实时性优化** - 使用`PREEMPT_RT`内核补丁提升实时性,或通过`roslaunch`调整节点优先级: ```xml <node pkg="move_base" name="move_base" type="move_base" launch-prefix="chrt -f 99"/> ``` --- ### 四、安全机制部署 1. **紧急停止与监控** - 添加硬件急停开关,并通过`rosserial`与ROS连接: ```cpp // Arduino端代码片段 if (digitalRead(EMERGENCY_STOP_PIN) == LOW) { std_msgs::Bool msg; msg.data = true; emergency_pub.publish(msg); } ``` - 在ROS中监听急停话题并停止导航: ```python rospy.Subscriber('/emergency_stop', Bool, callback=stop_robot) ``` --- ### 五、分步验证流程 1. **单设备测试** - 单独测试每个传感器/执行器的驱动,确保数据流正常。 2. **验证** - 在空旷环境中运行`gmapping`,检查地与真实环境的一致性。 3. **导航调试** - 通过`rviz`发送目标点,观察路径规划是否避开实际障碍物。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值