官方地址

官方地址

官方最新用户文档-V6.0.0

码云镜像-activiti-7-developers-guide

关于BPMN

BPMN(Business Process Model AndNotation)- 业务流程模型和符号 是由BPMI(BusinessProcess Management Initiative)开发的一套标准的业务流程建模符号,使用BPMN提供的符号可以创建业务流程。2004年5月发布了BPMN1.0规范.BPMI于2005年9月并入OMG(The Object Management Group对象管理组织)组织OMG于2011年1月发布BPMN2.0的最终版本。

Activiti 就是使用 BPMN 2.0 进行流程建模、流程执行管理,它包括很多的建模符号。

可以使用这些符号来绘制流程图,例如下图:

Activiti也是通过将这些流程图的BPMN文件部署到数据库中,然后启动相应的流程,来完成工作流的一个映射。这些节点可以指定一些参数、表达式、绑定事件或者绑定解析处理类,来实现对每个流程节点的处理。

对于BPMN流程图的绘制方法这里就不做赘述,因为我也不是了解很多,网上有许多非常好的文章可供参考。

Activiti 的架构

引擎API是与Activiti交互的最常见方式。中心起点是ProcessEngine,可以按照配置部分中所述的多种方式创建 。从ProcessEngine,您可以获得包含工作流/ BPM方法的各种服务。ProcessEngine和服务对象是线程安全的。因此,您可以为整个服务器保留对其中之一的引用。

api.services

Activiti的工作流程是通过读取一个配置文件,然后得到一个工作流引擎实例,通过这个引擎可以获取多个不同模块的Service,然后就可以使用这些Service去完成相应的接口,比如部署会使用到RepositoryService,实例会用到RuntimeService等。(注意:FormServiceIdentityService已经在新版本中删除了)

// 获取引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 获取Service
RuntimeService runtimeService = processEngine.getRuntimeService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();
ManagementService managementService = processEngine.getManagementService();
IdentityService identityService = processEngine.getIdentityService();
HistoryService historyService = processEngine.getHistoryService();
FormService formService = processEngine.getFormService();
DynamicBpmnService dynamicBpmnService = processEngine.getDynamicBpmnService();

Service总览

Service名称 作用
RepositoryService 资源管理服务
RuntimeService 流程运行管理类
TaskService 任务管理类
HistoryService 历史管理类
ManagerService 引擎管理类

Activiti API

activiti运行流程

流程部署

在使用Activiti进行流程管理之前,首先需要将建模工具绘制的业务流程图部署到数据库中,这个时候就需要使用RepositoryService,可以通过RepositoryService进行流程部署、查询流程定义、暂停或激活发布的流程定义等。官方教程

部署流程

ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
Deployment deploy = deploymentBuilder
.addClasspathResource("bpmn/stadiumapplication.bpmn")
.name("球场申请流程")
.deploy();

部署方式

ReposityService主要就是依靠调用DeploymentBuilder的接口来进行流程定义的部署的。DeploymentBuilder支持多种方式的部署。

public interface DeploymentBuilder {
// 文件流方式部署
DeploymentBuilder addInputStream(String resourceName, InputStream inputStream);
DeploymentBuilder addInputStream(String resourceName,Resource resource);
// 资源文件方式部署
DeploymentBuilder addClasspathResource(String resource);
// 字符串内容部署,一般是bpmn的xml内容字符串部署
DeploymentBuilder addString(String resourceName, String text);
// 字节数组部署
DeploymentBuilder addBytes(String resourceName, byte[] bytes);
// Zip压缩包部署
DeploymentBuilder addZipInputStream(ZipInputStream zipInputStream);
// Bpmn模型部署,可动态生成BPMN model进行部署。
DeploymentBuilder addBpmnModel(String resourceName, BpmnModel bpmnModel); DeploymentBuilder setProjectManifest(ProjectManifest projectManifest); DeploymentBuilder setEnforcedAppVersion(Integer enforcedAppVersion); /**
*如果调用,则不会对bpmn2.0xsd进行XML模式验证。
*一般不推荐
*/
DeploymentBuilder disableSchemaValidation(); /**
* 如果调用,则不会对流程定义进行验证,以确定流程定义在引擎上是可执行的。
* 一般不推荐使用。
*/
DeploymentBuilder disableBpmnValidation(); /**
* 为部署指定名字
*/
DeploymentBuilder name(String name); /**
* 为部署指定种类
*/
DeploymentBuilder category(String category); /**
* 为部署指定Key,该属性默认是bpmn的id
*/
DeploymentBuilder key(String key); /**
* 为部署指定租户ID,没有用到过。。。
*/
DeploymentBuilder tenantId(String tenantId); /**
* 如果已设置,则此部署将与以前的任何部署进行比较。这意味着每个(未生成的)资源都将与此部署提供的资源进行比较。
*/
DeploymentBuilder enableDuplicateFiltering(); /**
* 设置激活此部署中包含的流程定义的日期。这意味着所有流程定义都将像往常一样部署,但它们将从在给定的激活日期之前开始。
*/
DeploymentBuilder activateProcessDefinitionsOn(Date date); /**
* 允许将影响部署的属性添加到实例中
*/
DeploymentBuilder deploymentProperty(String propertyKey, Object propertyValue); /**
* 将所有提供的源部署到Activiti引擎。
*/
Deployment deploy(); }

涉及数据库表

表名 作用
ACT_RE_DEPLOYMENT 流程定义部署表:每部署一次增加一条记录
ACT_RE_PROCDEF 流程定义表:部署每个新的流程都会在这张表上增加一条记录
ACT_GE_BYTEARRAY 流程资源表:存储xml和png内容的表
  • ACT_GE_BYTEARRAY

    该表记录了流程的资源信息,包括bpmn文件的xml内容和png图片的二进制内容,RepositoryService也提供了相应的接口去获取这些资源。

    SELECT * FROM ACT_GE_BYTEARRAY;
    ID_ REV_ NAME_ DEPLOYMENT_ID_ BYTES_ GENERATED_
    2 1 bpmn/stadiumapplication.bpmn 1 (BLOB)4.43
  • ACT_RE_DEPLOYMENT

    SELECT * FROM ACT_RE_DEPLOYMENT;
    ID_ NAME_ CATEGORY_ KEY_ TENANT_ID_ DEPLOY_TIME_ ENGINE_VERSION_
    1 球场申请流程 2021-01-17 08:59:05
  • ACT_RE_PROCDEF

    SELECT * FROM ACT_RE_PROCDEF;
    ID_ REV_ CATEGORY_ NAME_ KEY_ VERSION_ DEPLOYMENT_ID_ RESOURCE_NAME_ DGRM_RESOURCE_NAME_ DESCRIPTION_ HAS_START_FORM_KEY_ HAS_GRAPHICAL_NOTATION_ SUSPENSION_STATE_ TENANT_ID_ ENGINE_VERSION_
    ballrepally:1:3 1 http://www.activiti.org/testm1574124674914 球场申请 ballrepally 1 1 bpmn/stadiumapplication.bpmn 0 1 1

    KEY_ 这个字段是用来唯一识别不同流程的关键字。

    ACT_RE_DEPLOYMENTACT_RE_PROCDEF是一对多的关系,即:一次部署可以部署多个流程定义,而只会在流程部署表(ACT_RE_DEPLOYMENT)生成一条记录。

    一般情况下建议一次部署只部署一个流程。

查询流程信息

  • 查询流程部署信息

    // 获取流程部署查询器
    DeploymentQuery deploymentQuery = repositoryService.createDeploymentQuery();
    // 查询列表。
    List<Deployment> deployments = deploymentQuery.list();
  • 查询流程定义信息

    // 获取流程定义查询器
    ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
    // 查询器构建查询条件,最终返回列表。
    List<ProcessDefinition> processDefinitionList = processDefinitionQuery
    .orderByProcessDefinitionVersion()
    .asc()
    .list();
  • 查询部署资源

    public interface RepositoryService {
    
      /**
    * 通过部署ID和资源名称获取资源流。
    * @param deploymentId 部署ID
    * @param resourceName 资源名称
    */
    InputStream getResourceAsStream(String deploymentId, String resourceName); /**
    * 检索给定部署的部署资源列表,按字母顺序排列。
    * @param deploymentId 部署ID
    */
    List<String> getDeploymentResourceNames(String deploymentId); /**
    * 允许通过字节流访问已部署的流程模型,例如BPMN2.0XML文件。
    * @param processDefinitionId 流程定义ID
    */
    InputStream getProcessModel(String processDefinitionId); /**
    * 返回流程定义信息
    * @param processDefinitionId 流程定义ID
    */
    ProcessDefinition getProcessDefinition(String processDefinitionId); /**
    * 返回与具有提供的进程定义id的进程定义对应的BPMN 模型。
    * @param processDefinitionId 流程定义ID
    */
    BpmnModel getBpmnModel(String processDefinitionId); }

挂起流程

public interface RepositoryService {

   /**
* 挂起给定id的流程定义。
*/
void suspendProcessDefinitionById(String processDefinitionId); /**
* 挂起给定id的流程定义。
*
* @param suspendProcessInstances 是否挂起流程实例
* 如果为true,则提供的流程定义的所有流程实例也将挂起
* @param suspensionDate 挂起日期
* 进程定义将被挂起的日期。如果为null,进程定义将立即挂起。注意:作业执行器需要处于活动状态才能使用此命令!
*/
void suspendProcessDefinitionById(String processDefinitionId,
boolean suspendProcessInstances,
Date suspensionDate); /**
* 挂起给定流程定义Key的流程
*/
void suspendProcessDefinitionByKey(String processDefinitionKey); /**
* 挂起给定流程定义Key的流程
* @param suspendProcessInstances 是否挂起流程实例
* 如果为true,则提供的流程定义的所有流程实例也将挂起
* @param suspensionDate 挂起日期
* 进程定义将被挂起的日期。如果为null,进程定义将立即挂起。注意:作业执行器需要处于活动状态才能使用此命令!
*/
void suspendProcessDefinitionByKey(String processDefinitionKey,
boolean suspendProcessInstances,
Date suspensionDate); /**
* 挂起给定流程定义Key和租户Id的流程。
*/
void suspendProcessDefinitionByKey(String processDefinitionKey, String tenantId); ...
}

激活流程

public interface RepositoryService {
/**
* 激活指定流程定义ID的流程
*/
void activateProcessDefinitionById(String processDefinitionId); /**
* 激活指定流程定义ID的流程
*
* @param suspendProcessInstances 是否挂起流程实例
* 如果为true,则提供的流程定义的所有流程实例也将激活
* @param suspensionDate 挂起日期
* 进程定义将被激活的日期。如果为null,进程定义将立即激活。注意:作业执行器需要处于挂起状态才能使用此命令!
*/
void activateProcessDefinitionById(String processDefinitionId,
boolean activateProcessInstances,
Date activationDate); /**
* 激活给定流程定义Key(BPMN图中的ID)的流程
*/
void activateProcessDefinitionByKey(String processDefinitionKey); /**
* 激活指定流程定义Key的流程
*
* @param suspendProcessInstances 是否挂起流程实例
* 如果为true,则提供的流程定义的所有流程实例也将激活
* @param suspensionDate 挂起日期
* 进程定义将被激活的日期。如果为null,进程定义将立即激活。注意:作业执行器需要处于挂起状态才能使用此命令!
*/
void activateProcessDefinitionByKey(String processDefinitionKey,
boolean activateProcessInstances,
Date activationDate); /**
* 激活给定流程定义Key和租户Id的流程
*/
void activateProcessDefinitionByKey(String processDefinitionKey, String tenantId); ...
}

删除流程

public interface RepositoryService {

  /**
* 删除指定流程.
* @param deploymentId 流程部署ID
*/
void deleteDeployment(String deploymentId); /**
* 删除给定的部署和级联删除到流程实例、历史流程实例和作业。
*
* @param deploymentId 流程部署ID
* @param cascade 是否级联删除
*/
void deleteDeployment(String deploymentId, boolean cascade); }

流程实例

当我们将流程部署完成之后,先要使用他就得先开始一个流程实例。所谓流程实例,即比如我们部署了一个请假流程,小李的请假流程就是一个实例,小王的请假流程也是一个实例。所以我们的流程实例也是基于流程部署来实现的。

流程实例的管理底层使用的是RuntimeService,而最新提供的ProcessRuntime对流程进行了二次封装,简便了api的调用。

启动流程实例

  ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
public interface RuntimeService {

    /**
* 启动先前创建的流程实例。
* @param createdProcessInstance 已经创建的流程实例
*/
ProcessInstance startCreatedProcessInstance(ProcessInstance createdProcessInstance, Map<String, Object> variables); /**
* 开启指定流程定义Key的最新版本
* @param processDefinitionKey 流程定义的Key
*/
ProcessInstance startProcessInstanceByKey(String processDefinitionKey); /**
* 开启给定流程定义Key的最新版本,并指定业务流程Key
* @param processDefinitionKey 流程定义Key
* @param businessKey 业务流程Key
*/
ProcessInstance startProcessInstanceByKey(String processDefinitionKey, String businessKey); /**
* 开启指定流程定义Key的最新版本
*
* @param processDefinitionKey 流程定义的Key
* @param variables 流程的参数,比如uel参数
*/
ProcessInstance startProcessInstanceByKey(String processDefinitionKey, Map<String, Object> variables); /**
* 开启给定流程定义Key的最新版本,并指定业务流程Key
* @param processDefinitionKey 流程定义Key
* @param variables 参数
* @param businessKey 业务Key
*/
ProcessInstance startProcessInstanceByKey(String processDefinitionKey, String businessKey, Map<String, Object> variables); /**
* 开启指定流程定义Id的最新版本.
* @param processDefinitionId 流程定义Id
*/
ProcessInstance startProcessInstanceById(String processDefinitionId); /**
* 开启给定流程定义Id的最新版本,并指定业务流程Key
* @param processDefinitionId 流程定义ID
*/
ProcessInstance startProcessInstanceById(String processDefinitionId, String businessKey); /**
* 开启给定流程定义Id的最新版本
* @param processDefinitionId 流程定义Id
* @param variables 参数
*/
ProcessInstance startProcessInstanceById(String processDefinitionId, Map<String, Object> variables); /**
* 开启给定流程定义Id的最新版本
* @param processDefinitionId 流程定义Id
* @param variables 参数
*/
ProcessInstance startProcessInstanceById(String processDefinitionId, String businessKey, Map<String, Object> variables); }

涉及数据库表

  • ACT_RU_EXECTION

    流程实例执行表,记录当前流程实例的执行情况。流程实例执行,如果当前只有一个分支时,一个流程实例只有一条记录且执行表的主键id和流程实例id相同,如果当前有多个分支正在运行则该执行表中有多条记录,存在执行表的主键和流程实例id不相同的记录。

    不论当前有几个分支总会有一条记录的执行表的主键和流程实例id相同

    一个流程实例运行完成,此表中与流程实例相关的记录删除。

  • ACT_RU_TASK

    任务执行表,记录当前执行的任务。启动流程实例,流程当前执行到第一个任务结点,此表会插入一条记录表示当前任务的执行情况,如果任务完成则记录删除。

  • ACT_RU_IDENTITYLINK

    任务参与者,记录当前参与任务的用户或组。

  • ACT_HI_PROCINST

    流程实例历史表,流程实例启动,会在此表插入一条记录,流程实例运行完成记录也不会删除。

  • ACT_HI_TASKINST

    任务历史表,记录所有任务,开始一个任务,不仅在act_ru_task表插入记录,也会在历史任务表插入一条记录,任务历史表的主键就是任务id,任务完成此表记录不删除。

  • ACT_HI_ACTINST

    活动历史表,记录所有活动,活动包括任务,所以此表中不仅记录了任务,还记录了流程执行过程的其它活动,比如开始事件、结束事件。

    挂起流程实例

/**
* 挂起指定流程Id的流程实例
*/
void suspendProcessInstanceById(String processInstanceId);

激活流程实例

/**
* 激活指定流程Id的流程实例
*/
void activateProcessInstanceById(String processInstanceId);

删除流程实例

/**
* 删除实例
* @param processInstanceId 流程实例Id
* @param deleteReason 删除原因
*/
void deleteProcessInstance(String processInstanceId, String deleteReason);

获取传入参数

/**
* 给定执行作用域(包括父作用域)中可见的所有变量。
* @param executionId 启动的实例的ID
*/
Map<String, Object> getVariables(String executionId);

任务管理

任务管理的接口是通过TaskService来实现的。而最新的是使用TaskRuntime接口来实现的,当然TaskRuntime底层也是通过TaskService来实现的。

ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();

获取我的任务

// 获取任务查询器
TaskQuery taskQuery = taskService.createTaskQuery();
// 查询器获取列表
List<Task> tasks = taskQuery.taskAssignee("you ID").list();

拾取任务

有的时候,某个任务节点可能没有直接指定责任人,但是指定了候选人列表或者候选组。这个时候你还是可以将这个任务查询出来的,但是需要主动拾取任务。

TaskQuery taskQuery = taskService.createTaskQuery();
Task task = taskQuery.processInstanceBusinessKey("businessKey").singleResult();
// 拾取任务
taskService.claim(task.getId(),"userId");

解开任务

如果拾取任务错了或者想将任务转给别人,可以调用解开任务的接口,其实也就是将责任人置空

taskService.unclaim(String taskId);

完成任务

/**
* 在任务成功执行时调用,并且所需的任务参数由最终用户给定。
* @param taskId 任务ID
* @param variables 参数对象
*/
void complete(String taskId, Map<String, Object> variables);


Activiti7主要对象与接口

对象

  • Deployment:流程部署对象,部署一个流程时创建
  • ProcessDefinitions;流程定义,部署成功后自动创建
  • ProcessInstances:流程实例,启动流程时创建
  • Task:任务,在Activiti中Task仅指有角色参与的任务,即定义中的UserTask
  • Execution:执行计划,流程实例和流程执行中的所以节点都是Execution

接口

  • ProcessEngine:流程引擎的抽象,通过它我们可以获得我们需要的一切服务
  • RepositoryServicez:Acitviti中每一个不同版本的业务流程的定义都需要使用一些定义文件,部署文件和支持数据(例如BPMN2.0 XML文件,表单定义文件,流程定义图像文件等),这些文件都存储在Activiti内建的Repository中。RepositoryService提供了对repository的存取服务
  • RuntimeService:在Activi中,每当一个流程定义被启动一次之后,都会生成一个相应的流程对象实例。RuntimeService提供了启动流程,查询流程实例,设置获取流程实例变量等功能。此外它还提供了对流程部署,流程定义和流程实例的存取服务
  • TaskService:在Activiti中业务流程定义中的每一个执行节点被称为一个Task,对流程中的数据存取,状态变更等操作均需要在Task中完成。TaskService提供了对用户Task和form相关的操作。它提供了运行时任务查询,领取,完成,删除以及变量设置等功能
  • IdentityServjice:Activiti中内置了用户以及组管理的功能,必须使用这些用户和组的信息才能获取到相应的Task。IdentityService提供了对Activiti系统中的用户和组的管理功能
  • ManagementService:提供了对Activiti流程引擎的管理和维护功能,
  • HistoryService:用于获取正在运行或已经完成的流程实例的信息,与RuntimeService中获取的流程信息不同,历史信息包含已经持久化存储的永久信息,并已经被针对查询优化

原文链接

Activiti初学者教程链接

Activiti7基本介绍的更多相关文章

  1. Activiti7 表介绍

    由于Activiti自生成的表较多,这里先对activiti自生成数据库表进行介绍. 数据库表的创建在后续的demo文章中进行介绍,并且后续会写一篇关于数据库详解的文章,这里先大概知道Activiti ...

  2. Activiti7新的API介绍

    一.Activiti7 的组成部分 Activiti Core 作为Activiti 的核心部分,Activiti Cloud 主要是利用云服务来实现分布式业务流程开发. 二.Activiti 新的 ...

  3. Activiti7整合SpringBoot(十二)

    1 SpringBoot 整合 Activiti7 的配置 为了能够实现 SpringBoot 与 Activiti7 整合开发,首先我们要引入相关的依赖支持.所以,我们在工程的 pom.xml 文件 ...

  4. Activiti7工作流+SpringBoot

    文章目录 一. Activiti相关概念 1. Activiti介绍 2. 核心类 2.1 ProcessEngine 2.2 服务(Service)类 2.2.1 TaskService 2.2.2 ...

  5. springboot2整合activiti7具体步骤

    写在前面 需要提前了解的内容有 springboot.springSecurity.activiti基本使用 关于activiti Activiti项目是一项新的基于Apache许可的开源BPM平台, ...

  6. SpringBoot系列——Activiti7工作流引擎

    前言 工作流程是我们日常开发项目中常见的功能,本文记录springboot整合activiti7. Activiti介绍 官网:https://www.activiti.org 数据库表 act_hi ...

  7. CSS3 background-image背景图片相关介绍

    这里将会介绍如何通过background-image设置背景图片,以及背景图片的平铺.拉伸.偏移.设置大小等操作. 1. 背景图片样式分类 CSS中设置元素背景图片及其背景图片样式的属性主要以下几个: ...

  8. MySQL高级知识- MySQL的架构介绍

    [TOC] 1.MySQL 简介 概述 MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,目前属于Oracle公司. MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而 ...

  9. Windows Server 2012 NIC Teaming介绍及注意事项

    Windows Server 2012 NIC Teaming介绍及注意事项 转载自:http://www.it165.net/os/html/201303/4799.html Windows Ser ...

随机推荐

  1. 远见而明察近观若明火|Centos7.6环境基于Prometheus和Grafana结合钉钉机器人打造全时监控(预警)Docker容器服务系统

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_181 我们知道,奉行长期主义的网络公司,势必应在软件开发流程管理体系上具备规范意识,即代码提交有CR(CodeReview),功能 ...

  2. Odoo14 需要哪些技术

    1 PostgreSQL:数据库,存储数据. 2 Python :主要作用是控制数据库,如:建表.关联字段.批量数据-- 3 html.css.javascript:基础前端. 4 scss:前端样式 ...

  3. 后端统一处理返回前端日期LocalDateTime格式化去T,Long返回前端损失精度问题

    一.前言 我们在实际开发中肯定会遇到后端的时间传到前端是这个样子的:2022-08-02T15:43:50 这个时候前后端就开始踢皮球了,!! 后端说:前端来做就可! 前端说:后端来做就可! 作为一名 ...

  4. 【PMP学习笔记】第4章 项目整合管理

    [PMP学习笔记]第4章 项目整合管理 一.项目整合管理 什么是项目整合管理? 项目整合管理由项目经理负责.虽然其他知识领域可以由相关专家(如成本分析专家.进度规划专家.风险管理专家)管理,但是项目整 ...

  5. Vim基础用法,最常用、最实用的命令介绍(保姆级教程)

    配置文件设置 set number (设置行号) set nocompatible (设置不兼容vi模式,不设置会导致许多vim特性被禁用) set clipboard=unnamed (设置普通的复 ...

  6. Tomcat启动失败 提示Server Tomcat v7.0 Server at localhost failed to start.六种解决方法

    Tomcat启动失败,提示Server Tomcat v7.0 Server at localhost failed to start 在一次查看自己以前写过的项目中,运行tomcat失败,出现如图提 ...

  7. ftp: connect: No route to host 解决方案

    实验环境: centos7 x2 server-vsftp:192.168.1.32 client:192.168.95 客户端测试访问 ftp服务器 报错:ftp: connect: No rout ...

  8. virtio_net设备的校验和问题

    我们来看一个virtio_net设备的校验和配置: [root@10 ~]# ethtool -K eth0 tx-checksumming on //caq:大写的K用来调整feature [roo ...

  9. 漏洞扫描工具awvs13破解

    AWVS13_windows破解版:链接:https://pan.baidu.com/s/1qeGiNubhWgY6oeMx_IkMtg 提取码:2i2o AWVS13_linux破解版:链接:htt ...

  10. [CF1525D] Armchairs (DP / 模拟费用流)

    题面简述 一条线上等距地分布着 n n n 老鼠和 m m m 洞( m ≥ n m\geq n m≥n),这连续 n + m n+m n+m 个位置上要么是老鼠要么是洞,一个老鼠进一个洞,代价是所有 ...