T2-U设备配网、重置与网络状态

TuyaOS 联网单品种的 Wi-Fi 设备需要连接路由器,并且连接到涂鸦云,才能实现正常智能控制。这样智能设备就会涉及到 以下相关流程:

  • 设备配网及涂鸦云激活
  • 已配网/已激活设备重置,重新进入配网状态
  • 当前网络状态显示

一、 设备配网

TuyaOS 开发框架提供了很多设备配网方案,例如 热点配网蓝牙配网闪电配网 等。T2-U 开发板当前 SDK 支持 热点配网蓝牙配网 这 2 种配网模式,设备配网模式的配置包含了选择设备 配网模式 和选择 配网启动方式

通过 APP 配网连接路由器后,设备自动会在涂鸦云激活,并与当前配网的用户账号做绑定。

1. 设备配网模式

TuyaOS 开发框架提供多种设备配网模式,主要通过 tuya_iot_wf_soc_dev_init_param() 函数第一个参数 GW_WF_CFG_MTHD_SEL cfg 设置。

/* tuya sdk definition of wifi work mode */
typedef BYTE_T GW_WF_CFG_MTHD_SEL; // wifi config method select
#define GWCM_OLD                0   // do not have low power mode
#define GWCM_LOW_POWER          1   // with low power mode
#define GWCM_SPCL_MODE          2   // special with low power mode
#define GWCM_OLD_PROD           3   // GWCM_OLD mode with product
#define GWCM_LOW_POWER_AUTOCFG 4   // with low power mode && auto cfg
#define GWCM_SPCL_AUTOCFG       5   // special with low power mode && auto cfg
配网模式的定义

注意:

配网模式中涉及到的低功耗,是指关闭射频模组,芯片并非进入了睡眠模式。低功耗状态下无法正常配网,需要通过外部条件触发手工调用

宏定义配网模式备注说明
GWCM_OLD / GWCM_OLD_PROD上电配网GWCM_OLD_PROD 模式在启动时会扫描成品产测的指定信标。
在 TuyaOS 3.0.0 及以上的版本里,扫描成品产测信标的功能由应用自行实现。
GWCM_LOW_POWER上电低功耗-
GWCM_SPCL_MODE上电低功耗和记忆重连-
GWCM_LOW_POWER_AUTOCFG上电配网和低功耗-
GWCM_SPCL_AUTOCFG上电配网和低功耗和记忆重连-

下面依次介绍上诉配网模式的区别:

  • 上电配网

    设备状态
    描述
    未配网上电就进入待配网状态
    待配网/配网中一直处于待配网状态
    配网激活后被移除进入待配网状态
  • 上电低功耗

    设备状态
    描述
    未配网上电默认低功耗状态,需调用 重置接口 才能进入配网状态
    待配网/配网中一段时间后(默认 3 min,您可以自定义)没有配网成功,设备自动进入低功耗状态。这时,如果重启设备,重启后的状态为:
    • 配网时间小于 10 秒,保持上次配网状态
    • 配网时间超过 10 秒,进入低功耗
    配网激活后被移除进入配网状态
  • 上电配网和低功耗

    设备状态
    描述
    未配网上电就进入配网状态
    待配网/配网中一段时间后(默认 3 min,您可以自定义)没有配网成功,设备自动进入低功耗状态
    配网激活后被移除进入配网状态
  • 上电低功耗和记忆重连

    设备状态
    描述
    未配网上电默认低功耗状态,需调用 重置接口 才能进入配网状态
    待配网/配网中
    • 本地移除后进入配网状态:一段时间后(默认 3 min,您可以自定义)没有配网成功,设备连接被移除前所连接的路由器(记忆重连)。这时,如果重启设备,重启后的状态为:
      • 配网时间小于 10 秒,保持上次配网状态
      • 配网时间超过 10 秒,连接被移除前所连接的路由器(记忆重连)
    • 从未配网切换到配网状态或者远程移除进入配网状态:一段时间后(默认 3 min,您可以自定义)没有配网成功,设备自动进入低功耗状态。这时,如果重启设备,重启后的状态为:
      • 配网时间小于 10 秒,保持上次配网状态
      • 配网时间超过 10 秒,进入低功耗
    配网激活后被移除进入配网状态
  • 上电配网、低功耗和记忆重连

    设备状态
    描述
    未配网上电就进入配网状态
    待配网/配网中
    • 本地移除后进入配网状态:一段时间后(默认 3 min,您可以自定义)没有配网成功,设备连接被移除前所连接的路由器(记忆重连)这时,如果重启设备,重启后的状态为:
      • 配网时间小于 10 秒,保持上次配网状态
      • 配网时间超过 10 秒,连接被移除前所连接的路由器(记忆重连)
    • 从未配网切换到配网状态或者远程移除进入配网状态:一段时间后(默认 3 min,您可以自定义)没有配网成功,设备自动进入低功耗状态。这时,如果重启设备,重启后的状态为:
      • 配网时间小于 10 秒,保持上次配网状态
      • 配网时间超过 10 秒,进入低功耗
配网激活后被移除 进入配网状态

2. 配网启动方式

TuyaOS 开发框架提供多种设备初始配网启动方式:

配网启动方式
描述
仅支持热点模式配网仅支持 热点模式配网,调用设备重置接口后设备启动还是热点配网
仅支持快连模式配网仅支持快连模式配网,调用设备重置接口后,还是快连模式配网
默认启动热点模式配网设备启动默认先进入热点模式配网,调用设备重置接口后会进入快连模式配网,以此循环
默认启动快连模式配网设备启动默认先进入快连模式配网,调用设备重置接口后进入热点模式配网,以此循环
万能配网任何配网方式都生效,热点模式配网,快连模式配网,闪电配网FFS 配网
配网启动方式主要通过 tuya_iot_wf_soc_dev_init_param() 函数第二个参数 GW_WF_START_MODE start_mode 设置。
/* tuya sdk definition of wifi start mode */
typedef BYTE_T GW_WF_START_MODE;
#define WF_START_AP_ONLY        0   // only have ap-cfg mode
#define WF_START_SMART_ONLY     1   // only have smart-cfg mode
#define WF_START_AP_FIRST       2   // have both ap-cfg and smart-cfg. default is ap-cfg mode
#define WF_START_SMART_FIRST    3   // have both ap-cfg and smart-cfg. default is smart-cfg mode
#define WF_START_SMART_AP_CONCURRENT    4   // ap-cfg and smart-cfg is concurrent
配网启动方式的定义
宏定义配网启动方式备注说明
WF_START_AP_ONLY仅支持热点模式配网-
WF_START_SMART_ONLY仅支持快连模式配网Tuyaos 3.4.0 及以上版本的框架不支持快连模式配网。
会被当成万能配网做兼容处理。
WF_START_AP_FIRST默认启动热点模式配网-
WF_START_SMART_FIRST默认启动快连模式配网Tuyaos 3.4.0 及以上版本的框架不支持快连模式配网。
故该启动方式会被当成万能配网做兼容处理。
WF_START_SMART_AP_CONCURRENT万能配网-

注意:

  1. Tuyaos 3.4.0 及以上版本不再支持快连模式配网,如果用户选择 默认启动热点模式配网默认启动快连模式配网 这两种启动方式,TuyaOS 默认使用 万能配网 的启动方式做兼容处理。
  2. 蓝牙配网 独立于任何一种配网方式,只要蓝牙服务及蓝牙配网功能是打开的,配网功能被启动时,框架会自动开启蓝牙配网功能。

TuyaOS 开发框架支持的配网模式,可以点击以下链接详细了解:

3. 配网超时时间设置

可通过该接口设置设备保持配网状态的时间。如果在该时间内设备没有配网成功,开发框架会停止配网,根据配网模式的设置进入相应的状态。

/**

 * @brief Set wifi netcfg timeout value in seconds
 *
 * @param[in] timeout_s time out value of netcfg.
 *
 * @return OPRT_OK on success. Others on error, please refer to tuya_error_code.h
 */

VOID set_wf_netcfg_timeout(UINT_T timeout_s);

二、 网络状态获取

网络状态 通过 tuya_iot_reg_get_wf_nw_stat_cb(__soc_dev_net_status_cb) 接口设置网络状态变化回调。设备网络状态变化的时候,TuyaOS 框架会调用该 API 注册进来的回调函数通知应用进行相应的处理。

TuyaOS 默认每 1 秒钟查询一次设备的网络状态,如果网络状态发生了变化,TuyaOS 会调用注册的回调接口,将变化通知给应用程序。

 /**
 * @brief tuya_iot_reg_get_wf_nw_stat_cb_params
 *
 * @param wf_nw_stat_cb
 * @param min_interval_s
 *
 * @return
 */
OPERATE_RET tuya_iot_reg_get_wf_nw_stat_cb_params(IN CONST GET_WF_NW_STAT_CB wf_nw_stat_cb, IN CONST INT_T min_interval_s);
#define tuya_iot_reg_get_wf_nw_stat_cb(wf_nw_stat_cb) \
    tuya_iot_reg_get_wf_nw_stat_cb_params(wf_nw_stat_cb, 1)

GET_WF_NW_STAT_CB wf_nw_stat_cb:网络状态变化通知回调,按照min_interval_s周期,检测网络状态,如果发生变化则会调用改回调接口。

示例回调:

/**
* @brief  SOC external network status change callback
*
* @param[in] stat: curr network status
* 
* @return none
*/
STATIC VOID __soc_dev_net_status_cb(IN CONST GW_WIFI_NW_STAT_E stat)
{
    TAL_PR_DEBUG("network status:%d", stat);
    if (STAT_CLOUD_CONN == stat) {
       /* 网络状态指示 */
    }
}

TuyaOS 定义的网络状态有以下几种:

/* tuya sdk definition of wifi-net status */
typedef BYTE_T GW_WIFI_NW_STAT_E;
#define STAT_LOW_POWER          0   // idle status,use to external config network
#define STAT_UNPROVISION        1   // smart config status
#define STAT_AP_STA_UNCFG       2   // ap WIFI config status
#define STAT_AP_STA_DISC        3   // ap WIFI already config,station disconnect
#define STAT_AP_STA_CONN        4   // ap station mode,station connect
#define STAT_STA_DISC           5   // only station mode,disconnect
#define STAT_STA_CONN           6   // station mode connect
#define STAT_CLOUD_CONN         7   // cloud connect
#define STAT_AP_CLOUD_CONN      8   // cloud connect and ap start
#define STAT_REG_FAIL           9   // register fail
#define STAT_OFFLINE            10   // offline
#define STAT_MQTT_ONLINE        11
#define STAT_MQTT_OFFLINE       12
#define STAT_UNPROVISION_AP_STA_UNCFG		13 //smart-cfg and ap-cfg concurrent config status
#define STAT_BT_ACTIVED         14

三、设备重置

设备重置是把设备恢复到配网、激活之前状态的操作。根据触发源不同,可以分为:

  • 本地重置
  • 远程重置

根据重置后设备所处的状态,又可细分为:

  • 普通重置:指解除绑定
  • 恢复出厂重置:指解除绑定清除数据

设备重置的时候,还会和设备配网模式关联起来,具体细节请参考 设备配网模式

设备重置主要适用场景如下:

  • 让设备进入配网状态:如支持 记忆模式 的设备,通过设备重置功能进入配网状态。
  • 让设备退网/解除绑定:如设备需要更换连接的路由器。
  • 让设备恢复出厂/清除用户数据:如电工类产品,清除设备本地存储的电量计量数据。

注意:

对于支持 Wi-Fi 网络备份闪电配网 的设备,无需执行设备重置即可完成路由器变更。

TuyaOS 开发框架执行完自身的重置任务后会通过回调函数通知开发者设备被重置了,方便开发者在回调中执行自定义的重置行为。最后开发框架会调用 Reset 接口重启设备。设备重启完成后就会进入对应的配网状态。

1. 功能描述

  • 普通重置(解除绑定):
    • 清除本地保存的配网数据
    • 清除本地保存的定时数据
    • 通知云端执行解绑操作(云端不会清除设备的历史数据,如定时数据、设备名称等,再次配网绑定后用户可查看到)
    • 通过回调函数通知用户
  • 恢复出厂重置(解绑并清除数据):
    • 全部 普通重置 的数据
    • 通过回调函数通知用户
    • 本地保存的描述设备 DP 列表 (schema) 的数据
    • 通知云端执行解绑并清除数据(云端会清除设备的历史数据,相当于是全新的设备)
    • 通过回调函数通知用户

2. 相关接口

  • 本地重置时,需要应用主动调用下述 API
    • 普通重置(解除绑定):tuya_iot_wf_gw_unactive
    • 恢复出厂重置(解绑并清除数据):tuya_iot_wf_gw_reset
  • 远程重置时,开发者无需主动调用 API,开发框架会监听云端下发的指令,执行重置操作。
  • 本地重置和远程重置时,开发框架都会通过回调函数通知开发者。通知回调函数的注册可见设备初始化的 [IoT 设备功能回调函数](https://developer.tuya.com/cn/docs/iot-device-dev/TuyaOS-iot_abi_device_init?id=Kc67dkj0mxrne#title-9-IoT 设备功能回调函数)。
重置通知回调

重置通知回调函数,由应用通过设备初始化接口传入。无论何种重置方式,回调函数都会被调用,以执行自定义重置行为(如清除应用数据)。

/**
 * @brief Handler to process gateway reset
 *
 * @param[in] type Reset type, see GW_RESET_TYPE_E
 */
typedef VOID (*GW_RESET_IFM_CB)(GW_RESET_TYPE_E type);

  • 回调参数数据定义
typedef enum {
    GW_LOCAL_RESET_FACTORY = 0,    // 本地恢复出厂重置
    GW_REMOTE_UNACTIVE,                // App 普通重置(解除绑定)
    GW_LOCAL_UNACTIVE,                   // 本地普通重置(解除绑定)
    GW_REMOTE_RESET_FACTORY,       // App 恢复出厂重置
    GW_RESET_DATA_FACTORY,            // 激活时数据重置(详见 FAQ)
} GW_RESET_TYPE_E;
本地普通重置

开发者调用 tuya_iot_wf_gw_unactive() 函数完成本地普通重置, 该 API 会立即返回,具体重置操作将 异步执行,完成重置后将通过回调函数通知开发者。

/**
 * @brief Local unactive
 *
 * @return OPERATE_RET
 */
OPERATE_RET tuya_iot_wf_gw_unactive(VOID);

本地恢复出厂重置(解绑并清除数据)

开发者调用 tuya_iot_wf_gw_reset() 函数完成本地恢复出厂重置(解绑并清除数据),该 API 会立即返回,具体重置操作将 异步执行,完成重置后将通过回调函数通知开发者。

/**
 * @brief Local reset factory
 *
 * @return OPERATE_RET
 */
OPERATE_RET tuya_iot_wf_gw_reset(VOID);

本地快速重置(解除绑定)

开发者调用 tuya_iot_wf_gw_fast_unactive() 函数完成本地快速重置(接触绑定),该 API 同步执行 重置操作,在重置之后设备会重新启动,并且按照新的 mthdwifi_start_mode 进入配网状态。

/**
 * @brief Local fast unactive
 *
 * @param[in] mthd: new work mode after reboot
 * @param[in] wifi_start_mode: new netcfg mode after reboot
 *
 * @return OPERATE_RET
 */
OPERATE_RET tuya_iot_wf_gw_fast_unactive(GW_WF_CFG_MTHD_SEL mthd, GW_WF_START_MODE wifi_start_mode);

注意:

该 API 用于快速重置设备并重启,在 设备初始化 前调用。mthdwifi_start_mode 的具体细节见 设备配网模式

3. 设备重置接口使用示例


// 执行自定义重置行为
VOID __reset_cb(GW_RESET_TYPE_E type)
{
    if(GW_LOCAL_RESET_FACTORY == type) { // 本地恢复出厂重置

    } else if(GW_REMOTE_UNACTIVE == type) { // App 普通重置(解除绑定)

    } else if(GW_LOCAL_UNACTIVE == type) { // 本地普通重置(解除绑定)

    } else if(GW_REMOTE_RESET_FACTORY == type) { // App 恢复出厂重置

    } else if(GW_RESET_DATA_FACTORY == type) { // 激活时数据重置(详见 FAQ)

    }
}

// 设备初始化
int test_dev_init()
{
    TY_IOT_CBS_S cbs = {
        .gw_reset_cb = __reset_cb,
        //其他回调函数设置
    };


    // 调用设备初始化 API,把 cbs 传入
    // xxx
}

// 本地普通重置
void test_local_unactive()
{
    TUYA_CALL_ERR_LOG(tuya_iot_wf_gw_unactive());
}

// 本地恢复出厂重置
void test_local_reset()
{
    TUYA_CALL_ERR_LOG(tuya_iot_wf_gw_reset());
}

设备重置FAQ可点击以下链接了解:

https://developer.tuya.com/cn/docs/iot-device-dev/TuyaOS-iot_abi_device_reset?id=Kc67srci7m1jk#title-17-FAQ

四、示例

在 tuyaos_demo_quickstart 示例中,自带了按钮驱动 app_key.c 和 指示灯驱动 app_led.c 示例代码供参考

默认的配网按钮和网络状态指示灯为:

丝印名称备注
P26LED 引脚,高电平点亮
P7按键引脚,低电平有效,短按切换指示灯状态,长按进入配网状态

可以在 tuya_app_main.c 中修改

/* network button, LED pin */
#define KEY_PIN 7
#define LED_PIN 26

1. 重置

tuyaos_demo_quickstart 示例中是通过按钮长按 5 秒后调用 tuya_iot_wf_gw_unactive 让设备进入普通重置(解除绑定)状态

STATIC VOID_T app_key_task(VOID_T *args)
{
    OPERATE_RET op_ret = OPRT_OK;
    TUYA_GPIO_LEVEL_E read_level = TUYA_GPIO_LEVEL_HIGH;
    UINT32_T time_start = 0, timer_end = 0;

    for (;;) {
        tkl_gpio_read(sg_key_pin_id, &read_level);
        if (TUYA_GPIO_LEVEL_LOW == read_level) {
            tal_system_sleep(3);
            tkl_gpio_read(sg_key_pin_id, &read_level);
            if (TUYA_GPIO_LEVEL_LOW != read_level) {
                break; // jitter
            }

            time_start = tal_system_get_millisecond();
            while (TUYA_GPIO_LEVEL_LOW == read_level) {
                tal_system_sleep(30);
                tkl_gpio_read(sg_key_pin_id, &read_level);
                timer_end = tal_system_get_millisecond();

                if (timer_end - time_start >= LONE_PRESS_TIME) {
                    TAL_PR_DEBUG("long press, remove device");
                    /* long press, remove device */
                    op_ret = tuya_iot_wf_gw_unactive();
                    if (op_ret != OPRT_OK) {
                        TAL_PR_ERR("long press tuya_iot_wf_gw_unactive error, %d", op_ret);
                    }
                    break;
                }
            }

            if (timer_end - time_start > 50) {
                TAL_PR_DEBUG("normal press");
                /* normal press */
                if (get_led_status()) {
                    set_led_status(LED_OFF);
                }
                else {
                    set_led_status(LED_ON);
                }
                update_all_dp();
            } else {
                TAL_PR_DEBUG("time too short");
            }
        }

        tal_system_sleep(100);
    }

    return;
}

2. 设备重启

tuyaos_demo_quickstart 示例中是 在设备初始化时将 __soc_dev_restart_req_cb 回调注册至 TuyaOS,并在回调中调用tal_system_reset()完成重启。

/**
 * @brief SOC device process restart request entry
 *
 * @param[in] type: gateway reset type
 *
 * @return none
 */
STATIC VOID_T __soc_dev_restart_req_cb(GW_RESET_TYPE_E type)
{
    TAL_PR_DEBUG("SOC Rev Restart Req %d", type);
    if (GW_RESET_DATA_FACTORY != type) {
        // UserTODO Device process restart
        tal_system_reset();
    }

    return;
}
#include <bits/stdc++.h> using namespace std; typedef long long ll; #define rep(i,s,t) for(register ll i = s;i <= t;++i) #define per(i,t,s) for(register ll i = t;i >= s;--i) const ll N = 1e6 + 5; ll n; ll k; ll rt1; ll rt2; ll top; ll idx; ll ans; ll p[N] = {}; ll q[N] = {}; ll fa[N] = {}; ll st[N] = {}; ll sz[N] = {}; ll siz[N] = {}; ll dfn[N] = {}; ll son[N] = {}; vector<ll> g[N]; vector<ll> g1[N]; vector<ll> g2[N]; class binary_indexed_tree { private: ll t[N] = {}; public: inline void init() { memset(t,0,sizeof(t)); } inline ll lowbit(ll x) { return x & (-x); } inline void upd(ll x,ll k) { while(x <= n) { t[x] += k; x += lowbit(x); } } inline ll qry(ll x) { ll ans = 0; while(x) { ans += t[x]; x -= lowbit(x); } return ans; } }; binary_indexed_tree t1; binary_indexed_tree t2; inline ll read() { ll x = 0; ll y = 1; char c = getchar(); while(c < '0' || c > '9') { if(c == '-') y = -y; c = getchar(); } while(c >= '0' && c <= '9') { x = (x << 3) + (x << 1) + (c ^ '0'); c = getchar(); } return x * y; } inline void write(ll x) { if(x < 0) { putchar('-'); write(-x); return; } if(x > 9) write(x / 10); putchar(x % 10 + '0'); } inline void dfs(ll u) { siz[u] = 1; dfn[u] = ++idx; for(register auto v : g1[u]) { dfs(v); siz[u] += siz[v]; } } inline void dfs1(ll u) { st[++top] = u; g[u].clear(); if(top > k) fa[u] = st[top - k]; else fa[u] = 0; if(fa[u]) g[fa[u]].push_back(u); sz[u] = 1; son[u] = 0; for(auto v : g2[u]) { dfs1(v); if(sz[v] > sz[son[u]]) son[u] = v; sz[u] += sz[v]; } top--; } inline void ins(ll x,ll k) { t1.upd(dfn[x],k); t1.upd(dfn[x] + siz[x],-k); t2.upd(dfn[x],k); } inline ll query(ll x) { return t1.qry(dfn[x]) + t2.qry(dfn[x] + siz[x] - 1) - t2.qry(dfn[x] - 1); } inline void dfs3(ll u,ll k) { for(auto v : g[u]) { if(k == 1) ans += query(v); else if(k == 2) ins(v,1); else if(k == 3) ins(v,-1); } for(auto v : g2[u]) dfs3(v,k); } inline void dfs2(ll u,ll k) // k is keep flag { for(auto v: g2[u]) if(v != son[u]) dfs2(v,0); if(son[u]) dfs2(son[u],1); for(auto v: g2[u]) if(v != son[u]) { dfs3(v,1); dfs3(v,2); } for(register auto v : g[u]) ins(v,1); if(!k) dfs3(u,3); } // 添加重置全局状态的函数 inline void reset_global() { // 重置 DFS 相关全局状态 top = 0; idx = 0; // 重置树状数组在 cal 逻辑中处理,不在此重置 // 重置 DSU 相关数组 memset(sz, 0, sizeof(sz)); memset(son, 0, sizeof(son)); memset(fa, 0, sizeof(fa)); memset(st, 0, sizeof(st)); // g 数组在 dfs1 中每个节点清空,无需全局重置 } // 封装 cal 函数,第一段代码一致 inline void cal() { reset_global(); // 重置全局状态 dfs(rt1); // 在 T1 上 DFS t1.init(); // 清空树状数组 t2.init(); top = 0; dfs1(rt2); // 在 T2 上处理第 k 祖先 dfs2(rt2, 0); // DSU on tree } int main() { freopen("D.in","r",stdin); freopen("D.out","w",stdout); n = read(); k = read(); rep(i,1,n) p[i] = read(); rep(i,1,n) q[i] = read(); rep(i,1,n) { if(!p[i]) rt1 = i; else g1[p[i]].push_back(i); if(!q[i]) rt2 = i; else g2[q[i]].push_back(i); } // 第一次计算 cal(); // 交换树 rep(i,1,n) { swap(p[i],q[i]); swap(g1[i],g2[i]); } swap(rt1,rt2); // 第二次计算 cal(); write(ans); fclose(stdin); fclose(stdout); return 0; }小丁的树 题目描述 小丁拥有两棵均具有 n n 个顶点,编号集合为 { 1 , 2 , ⋯   , n } {1,2,⋯,n} 的有根树 T 1 , T 2 T 1 ​ ,T 2 ​ ,现在他需要计算这两棵树的相似程度。 为了计算,小丁定义了对于一棵树 T T 和 T T 上两个不同顶点 u , v u,v 的距离函数 d T ( u , v ) d T ​ (u,v),其定义为 u , v u,v 两个点距离成为祖先关系有多近,具体来说,对于所有在 T T 上为祖先关系的点对 ( u ′ , v ′ ) (u ′ ,v ′ ), dis ⁡ ( u , u ′ ) + dis ⁡ ( v , v ′ ) dis(u,u ′ )+dis(v,v ′ ) 的最小值即为 d T ( u , v ) d T ​ (u,v) 的值,其中 dis ⁡ ( u , v ) dis(u,v) 表示 u , v u,v 在树 T T 上的唯一简单路径包含的边数,即 u , v u,v 的距离。 点对 ( u ′ , v ′ ) (u ′ ,v ′ ) 为祖先关系,当且仅当 u ′ u ′ 是 v ′ v ′ 的祖先或 v ′ v ′ 是 u ′ u ′ 的祖先。(注意,每个点都是自己的祖先) 小丁心里还有一个参数 k k,如果节点对 ( u , v ) (u,v) 满足以下条件,称之为不相似的节点对: 1 ≤ u < v ≤ n 1≤u<v≤n " d T 1 ( u , v ) = 0 d T 1 ​ ​ (u,v)=0 且 d T 2 ( u , v ) > k d T 2 ​ ​ (u,v)>k“ 或 " d T 2 ( u , v ) = 0 d T 2 ​ ​ (u,v)=0 且 d T 1 ( u , v ) > k d T 1 ​ ​ (u,v)>k​“ 小丁认为,不相似的节点对越多, T 1 T 1 ​ 和 T 2 T 2 ​ 就越不相似,你能告诉他总共有多少不相似的节点对吗? 输入格式 第一行两个整数 n , k n,k,表示 T 1 T 1 ​ 和 T 2 T 2 ​ 的节点数和参数 k k。 第二行 n n 个正整数 p 1 , p 2 , ⋯   , p n p 1 ​ ,p 2 ​ ,⋯,p n ​ , T 1 T 1 ​ 中节点 i i 的父节点为 p i p i ​ ,特别的,若 p i = 0 p i ​ =0,则 i i 是 T 1 T 1 ​ 的根。 第三行 n n 个正整数 q 1 , q 2 , ⋯   , q n q 1 ​ ,q 2 ​ ,⋯,q n ​ , T 2 T 2 ​ 中节点 i i 的父节点为 q i q i ​ ,特别的,若 q i = 0 q i ​ =0,则 i i 是 T 2 T 2 ​ 的根。 输出格式 一行一个整数,表示不相似的节点对总数。 样例 1 输入 5 0 0 1 1 2 3 5 3 1 1 0 样例 1 输出 4 样例 1 解释 ( 2 , 3 ) , ( 2 , 4 ) , ( 2 , 5 ) , ( 4 , 5 ) (2,3),(2,4),(2,5),(4,5) 为不相似的节点对。 其余样例见下发文件。 数据规模约定 对于所有数据, 1 ≤ n ≤ 2 × 10 5 , 0 ≤ k < n , 0 ≤ p i , q i ≤ n 1≤n≤2×10 5 ,0≤k<n,0≤p i ​ ,q i ​ ≤n,且由 p i , q i p i ​ ,q i ​ 形成的是一棵 n n 个节点的有根树。 本题采用捆绑评测,你只有通过了一个子任务中所有测试点才能得到该子任务的分数。 Subtask 1(10pts): 1 ≤ n ≤ 100 1≤n≤100。 Subtask 220pts): 1 ≤ n ≤ 3000 1≤n≤3000。 Subtask 3(20pts): k = 0 k=0。 Subtask 4(10pts): 0 ≤ k ≤ 20 0≤k≤20。 Subtask 5(40pts):无特殊限制。 请详细注释上述代码的每一行,给出详细的解题思路,并完整按照代码流程模拟样例
最新发布
07-25
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值