洛谷 T292115 [传智杯 #5 练习赛] 树的变迁#并查集+逆向思维

news2024/11/30 10:45:15

802707b83270480cbbe133dd4069dfbf.png

3f07121dad8f425aa39f5a3f1ecf4fec.png 

a0c2d7f3a8a44d68ba54d828163b3278.png


 一道质量挺高的题(个人感觉),题目说了每次要删除一条边,分成两棵树,那么很容易想到用并查集去维护。但是问题在于如果要将原来那棵树分成新的两个树必然不能使用路径压缩,如图所示62f274cd981f44af8ea2fd242b0fac6d.png

如果使用了路径压缩那就会失去父子关系,就分不出两棵树了。按照这个思路每次分一次树就要沿着这个节点去找一下他的父节点曾经是否有过4,有的话就分到树A,没有的话就分到树B,这样才能分出新的两棵树。

每个节点都爬一次那不累死了吗

我们的难点在于分割出两棵树,但是对于并查集来说,分割是难事,但合并可不是难事。

正难则反!

正着来不行,那我们就把所有操作都逆过来,那最难的分割不就变成合并了吗!

7122397a361d4ae086eaf8f3110aa958.png

逆向操作,我们执行查询的时候,查询之前的所有删边和修改都要已经被执行。

往上走,就相当于还原每一步的历史值,所以要保留所有节点的原值。

首先是经典的并查集。

并查集怎么使用取决于自己的定义,比如

定义f[i]=j为,i节点在树j中

定义f[i]=j为,i节点在集合j中

不会并查集的移步【模板】并查集 - 洛谷

这道题输入貌似也挺多的,不会快读的可以看一下我的洛谷 P3905 道路重建#Spfa+快读_zzc大魔王的博客-CSDN博客 前半部分快读内容

逆向操作的话,那么删掉所有边的情况才是初始状态,也就是所有不需要被删的边,都加上

为了保证操作的正确性先原模原样的把输入都输入了(逃

struct {
    int opt,e,u,val,a;//a为原值
}o[MAXNM];

int find(int x){//并查集路径压缩
    if(f[x]==x)return x;
    else return f[x]= find(f[x]);
}//找到节点x所在的树

inline int read(){
    int x=0;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch xor 48),ch=getchar();
    return x;
}//一个平平无奇的快读

inline void READ(){
    n=read(),m=read();
    for(int i=1;i<=n;++i){
        a[i]=read();
    }
    for(int i=1;i<=n-1;++i){
        edge[i].first=read();
        edge[i].second=read();
    }
    for(int i=1;i<=m;++i){
        o[i].opt=read();
        if(o[i].opt==1){
            o[i].e=read();
            del[o[i].e]=true;//记录一下被删掉的边
        }else if(o[i].opt==2){
            o[i].u=read();
            o[i].val=read();
        }else if(o[i].opt==3){
            o[i].u=read();
        }
    }
}//单纯的输入,没有任何操作
//为了方便逆序操作,建立一个结构体把输入都存下来

inline void build(){//逆序操作的初始状态
    for(int i=1;i<=n;++i)//初始化并查集
        f[i]=i;
    for(int i=1;i<=n-1;++i){//不需要删的边建回去
        if(!del[i]){
            int f1= find(edge[i].first),f2= find(edge[i].second);
            f[f1]=f2;
        }
    }
}

 完成了上述必要的初始化操作之后我们就可以正式开始逆序操作了。

TLE代码(50分)

#include <bits/stdc++.h>
using namespace std;
const int MAXNM=1e5+1;

struct {
    int opt,e,u,val,a;//a为原值
}o[MAXNM];
int n,m,a[MAXNM],f[MAXNM];
stack<int>ans;
pair<int,int>edge[MAXNM];
bitset<MAXNM>del;
int find(int x){
    if(f[x]==x)return x;
    else return f[x]= find(f[x]);
}//找到节点x所在的树
inline int read(){
    int x=0;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch xor 48),ch=getchar();
    return x;
}
inline void READ(){
    n=read(),m=read();
    for(int i=1;i<=n;++i){
        a[i]=read();
    }
    for(int i=1;i<=n-1;++i){
        edge[i].first=read();
        edge[i].second=read();
    }
    for(int i=1;i<=m;++i){
        o[i].opt=read();
        if(o[i].opt==1){
            o[i].e=read();
            del[o[i].e]=true;//删边
        }else if(o[i].opt==2){
            o[i].u=read();
            o[i].val=read();
        }else if(o[i].opt==3){
            o[i].u=read();
        }
    }
}//单纯的输入,没有任何操作
inline void build(){
    //初始化并查集
    for(int i=1;i<=n;++i)
        f[i]=i;
    for(int i=1;i<=n-1;++i){
        if(!del[i]){
            int f1= find(edge[i].first),f2= find(edge[i].second);
            f[f1]=f2;
        }
    }
}//不需要删的建回去
// n个节点,n-1条边,m个操作
int main(){
    READ();
    build();
    for(int i=1;i<=m;++i){//记录要修改的原值,并修改
        if(o[i].opt==2){
            int u=o[i].u;
            o[i].a=a[u];
            a[u]=o[i].val;
        }
    }
    for(int i=m;i>=1;i--){//逆向操作
        int opt=o[i].opt;
        if(opt==1){//分割变合并
            int e=o[i].e;
            int u=edge[e].first,v=edge[e].second;
            f[find(u)]=find(v);
        }else if(opt==2){//返回历史原值
            int u=o[i].u;
            a[u]=o[i].a;
        }else if(opt==3){//求出u树的权值
            int u=o[i].u;
            int tot=0;
            for(int j=1;j<=n;++j){
                if(find(j)== find(u))tot+=a[j];
            }
            ans.push(tot);
        }
    }
    //由于执行的是逆序操作,答案逆序输出才是正序
    while(!ans.empty()){
        printf("%d\n",ans.top());
        ans.pop();
    }
    return 0;
}

显然,让我们超时的是操作3,一个n的复杂度才找到树的权值,理论上我们在合并这棵树的时候就可以做一次加法来求出树的权值,以及修改权值的时候稍作调整,所以再建立一个数组来求和即可。

AC代码

#include <bits/stdc++.h>
using namespace std;
const int MAXNM=1e5+1;

struct {
    int opt,e,u,val,a;//a为原值
}o[MAXNM];
int n,m,a[MAXNM],f[MAXNM],sum[MAXNM];
//定义f[i]=j为节点i在树j中.sum[i]=j为树i的权值为j
stack<int>ans;
pair<int,int>edge[MAXNM];
bitset<MAXNM>del;
int find(int x){
    if(f[x]==x)return x;
    else return f[x]= find(f[x]);
}//找到节点x所在的树
inline int read(){
    int x=0;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch xor 48),ch=getchar();
    return x;
}
inline void READ(){
    n=read(),m=read();
    for(int i=1;i<=n;++i){
        a[i]=read();
    }
    for(int i=1;i<=n-1;++i){
        edge[i].first=read();
        edge[i].second=read();
    }
    for(int i=1;i<=m;++i){
        o[i].opt=read();
        if(o[i].opt==1){
            o[i].e=read();
            del[o[i].e]=true;//删边
        }else if(o[i].opt==2){
            o[i].u=read();
            o[i].val=read();
        }else if(o[i].opt==3){
            o[i].u=read();
        }
    }
}//单纯的输入,没有任何操作
inline void build(){
    //初始化并查集
    for(int i=1;i<=n;++i)
        f[i]=i;
    //初始化每棵树的权值
    for(int i=1;i<=n;++i)
        sum[i]=a[i];
    for(int i=1;i<=n-1;++i){
        if(!del[i]){
            int f1= find(edge[i].first),f2= find(edge[i].second);
            f[f1]=f2;
            //f1并到f2
            sum[f2]+=sum[f1];
            sum[f1]=0;
        }
    }
}//不需要删的建回去
// n个节点,n-1条边,m个操作
int main(){
    READ();
    build();
    for(int i=1;i<=m;++i){//记录要修改的原值,并修改
        if(o[i].opt==2){
            int u=o[i].u;
            int fu= find(u);
            //a[u]变成了o[i].val,差值o[i].val-a[u]
            sum[fu]+=o[i].val-a[u];
            o[i].a=a[u];
            a[u]=o[i].val;
        }
    }
    for(int i=m;i>=1;i--){//逆向操作
        int opt=o[i].opt;
        if(opt==1){//分割变合并
            int e=o[i].e;
            int u=edge[e].first,v=edge[e].second;
            int fu= find(u),fv= find(v);
            f[fu]=fv;
            //fu并到fv
            sum[fv]+=sum[fu];
            sum[fu]=0;
        }else if(opt==2){//返回历史原值
            int u=o[i].u;
            int fu= find(u);
            //a[u]的值变为了o[i].a,差值为o[i].a-a[u]
            sum[fu]+=o[i].a-a[u];
            a[u]=o[i].a;
        }else if(opt==3){//求出u树的权值
            int u=o[i].u;
            int fu= find(u);
            ans.push(sum[fu]);
        }
    }
    //由于执行的是逆序操作,答案逆序输出才是正序
    while(!ans.empty()){
        printf("%d\n",ans.top());
        ans.pop();
    }
    return 0;
}

树并了之后,为了保持思维的连贯性,最好将先前的树清空。但是对于并查集来说,从零散数据合并成一个集合,先前被合并的集合必然不会再被使用,所以可以不清空。

88e4cbff1a0543d88871939d881a0af2.png

上述操作都是精细活,因为本身考点只是逆向思维和并查集,每一步都要仔细去写,包括数据类型也要好好的判断,不然容易WA。

这道题n*val最大情况为1e8,仅1亿,而int的大小是大于21亿的,所以不需要long long。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/26205.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据安全法vs国家标准,40+标准助力数据安全保驾护航~(附整理文档及pdf下载)

2021年9月1日&#xff0c;《中华人民共和国数据安全法》生效施行&#xff0c;如何助力数据安全法的贯彻落实&#xff0c;做到数据合法合规的使用需要建设一系列的数据安全标准体系。也就是说&#xff0c;有了数据安全相关标准&#xff0c;就是有了如何践行数据安全法落地实施的…

Oracle LiveLabs实验:Manage and Monitor Autonomous Database

概述 本研讨会中的实验将引导您完成开始使用 Oracle 自治数据库的所有步骤。 首先&#xff0c;您将创建一个 Oracle 自治数据库实例。 然后&#xff0c;您将练习使用自治数据库工具和 API 从不同位置以不同格式加载数据的几种方法。 您将使用 SQL 分析数据并使用 Oracle Analy…

JavaScript构造函数和原型:继承

ES6之前并没有给我们提供 extends 继承。我们可以通过构造函数原型对象模拟实现继承&#xff0c;被称为组合继承。 call() 调用这个函数, 并且修改函数运行时的 this 指向 fun.call(thisArg, arg1, arg2, ...) thisArg &#xff1a;当前调用函数 this 的指向对象 arg1&am…

weapp源码反编译步骤

一、下载解密工具 解密工具下载&#xff1a;地址 二、在微信电脑版&#xff0c;打开目标小程序,在如下的目录中可以找到小程序的目录 这是我自自定义的缓存目录&#xff0c;找不到自己缓存目录在哪儿的&#xff0c;手机给自己的文件助手发个文档&#xff0c;打开所在目录&…

高标准企业级安全性,华为云会议为线上沟通保驾护航

如今&#xff0c;全球企业正在以办公室为主的工作环境向混合型工作环境转变&#xff0c;线上会议逐渐成为企业的刚需。事实上通过云会议的形式进行的远程沟通协作&#xff0c;如线上招聘、培训、课堂等也已非常普及&#xff0c;不过随之而来的网络安全风险也愈发引起各界关注。…

Linux有多重要?冲30万年薪Linux学习入门指南

与 Windows 相比&#xff0c;Linux 相对来说更加安全稳定&#xff0c;而且Linux可以定制化或者选择不同的发行版&#xff0c;下面借鉴网上的一张图片展示下 Linux 与 Windows 的区别。 ​ ​简单说就是linux 性能更好&#xff0c;直接通过命令行底层运行 1.廉价&#xff08;免费…

热门资讯:超大规模数字产业生态正在加快构建!

数字化转型新闻简报&#xff1a; 近年来&#xff0c;面对数字技术突飞猛进、政府政策倾斜支持、新冠疫情反向刺激&#xff0c;国内企业均从各个环节开始着手进行数字化转型&#xff0c;以期达到提效降本的目的。了解更多关于数字化转型的资讯才能更好地进行数字化转型。 01、华…

[附源码]Python计算机毕业设计宠物短期寄养平台

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

Linux-挖矿木马清理

一、什么是挖矿木马 挖矿木马会占用CPU进行超频运算&#xff0c;从而占用主机大量的CPU资源&#xff0c;严重影响服务器上的其他应用的正常运行。黑客为了得到更多的算力资源&#xff0c;一般都会对全网进行无差别扫描&#xff0c;同时利用SSH爆破和漏洞利用等手段攻击主机。部…

苹果钱包(AppleWallet)接入操作手册,超详细

一、写在前面 1.1 起源 最近有一点忙&#xff0c;更新博客出现了一些延迟。由于在工作中使用了Apple Wallet&#xff0c;所以在结束后准备以此记录一下。希望后面有要做此功能的同学&#xff0c;能够有所启发&#xff0c;觉得有帮助的&#xff0c;记得点赞收藏关注哦~ 1.2 A…

C++多态

目录 多态的概念 多态的定义和实现 深入理解多态 C11 override 和 final 重载、覆盖(重写)、隐藏(重定义)的对比 抽象类 多态的原理 动态绑定与静态绑定 单继承和多继承关系的虚函数表 多态的概念 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具…

SSM整合(五)

SSM整合之事务管理(一) 1.核心准备工作 1.1 导入spring-tx依赖 <!-- 事务spring-tx --> <dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>5.3.22</version> </dependen…

【附源码】计算机毕业设计JAVA研究生招生信息管理

【附源码】计算机毕业设计JAVA研究生招生信息管理 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JAVA …

旧苹果短信导入新苹果手机上,iphone短信迁移

概述&#xff1a;随着科技的快速发展&#xff0c;手机的更新换代周期也变得越来越短。那么问题来了&#xff1a;旧苹果短信导入新苹果手机上&#xff1f;相信这是新机主心中的痛点&#xff0c;那么今天小编就来教大家如何解决这个问题。 方法一、使用易我手机数据传输软件转移苹…

D0x-17(anti—Sp17)-3C12/TPGS抗精子蛋白单克隆抗体/维生素E聚乙二醇琥珀酸酯偶联阿霉素研究

下面分享了D0x-17(anti—Sp17)-3C12/TPGS抗精子蛋白单克隆抗体/维生素E聚乙二醇琥珀酸酯偶联阿霉素研究方法&#xff0c;来看&#xff01; D0x-TPGS聚乙二醇1000维生素E琥珀酸酯(TPGS)修饰阿霉素研究方法&#xff1a; 用硫酸铵梯度法制备TPGS修饰的阿霉素脂质体,并对其理化性质…

try/catch/finally的各种情况

众所周知&#xff0c;try语句报错&#xff0c;会执行catch语句&#xff0c;然后执行finally&#xff0c;以下这几种情况&#xff0c;看看会如何输出。 1、try语句中包含return&#xff0c;finally包含输出语句 public static void main(String[] args) {// write your code h…

YOLOX代码、预测(使用摄像头实时预测)及其添加SE注意力前后的实验结果

1. 代码获取 https://github.com/Le0v1n/ml_code/tree/main/ObjectDetection/YOLOX 如果代码对你有用&#xff0c;请star一下❤️ 2. 预测 2.1 图片预测 下载权值文件&#xff1a;https://github.com/Megvii-BaseDetection/YOLOX/releases/download/0.1.1rc0/yolox_s.pth将权…

iOS键盘通知弹框使用小结

项目开发中文本框输入的时候经常会用到键盘弹框遮挡的问题。解决办法就是根据底部键盘弹出的高度动态的改变对应view的位置。这里以多行文本框输入为例&#xff0c;效果图如下。 //第一步&#xff0c;注册监听键盘通知 [[NSNotificationCenter defaultCenter] addObserver:self…

只会 Python 不行,不会 Python 万万不行

当下的环境大家有目共睹&#xff0c;未来一段时间情况如何&#xff0c;想必不少人心里也清楚&#xff0c;技术人走到中年&#xff0c;难免会焦虑&#xff0c;职场上干得不爽&#xff0c;但是跳槽也不容易&#xff0c;加上不少企业裁员&#xff0c;换个满意的工作更是难上加难。…

JavaScript——周技能检测——菜单编辑——2022年11月22日(考完)

JavaScript——周技能检测——菜单编辑——2022年11月22日&#xff08;考完&#xff09; 一、语言和环境 1. 实现语言&#xff1a;JavaScript。 2. 开发环境&#xff1a;VScode。 二、要求 1、在文本框中输入两个操作数和选择运算符后&#xff0c;在页面上显示输出结果。 …