CF1416D 做题心得
CF1416D 做题心得
上次在某trick中提到了这个题,一开始觉得太毒瘤没有写,现在把它补上了。
感觉实现这个东西,比单纯收获一个trick,收获的东西多太多了。
主要思路
它的主要trick是“反向反向操作日神仙”,也就是,先删掉所有边,反过来做一遍,然后再用撤销的方式正过来再做一遍。
思路的框架就是,先把边都删掉,然后做一个Kruskal重构树。Kruskal重构树有一个性质就是,每一个子树都和某时刻的某联通块对应。
而我们在删边的过程中,每一个点对应的联通块就相当于,先把所有边都删掉,然后把它后面的删边操作都加上去后,它所在的联通块。那这样每个点管一个后缀,容易想到反过来撤销删边操作(即加边)。在这个过程中我们可以知道每个点对应的联通块的根节点 \(anc\)(换句话说,Kruskal重构树最终形态上以 \(anc\) 为根的子树就是这个点当时的联通块)。
然后就相当于每次从一个子树中选最大值,并将其修改为0。在dfs序上线段树做,trival。
细节
注意我们每次要找到联通块的根节点,但是我们还不好轻易的改树形态(路径压缩),Kruskal重构树也没有按秩合并一说。那怎么优化呢?
开两个数组,一个路径压缩,一个不路径压缩,存真实的树。压缩的那个保证复杂度,不压缩的那个保证正确性。俩分开使用。
代码
#include <bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
    #define N 500005
    #define F(i,l,r) for(int i=l;i<=r;++i)
    #define D(i,r,l) for(int i=r;i>=l;--i)
    #define Fs(i,l,r,c) for(int i=l;i<=r;c)
    #define Ds(i,r,l,c) for(int i=r;i>=l;c)
    #define MEM(x,a) memset(x,a,sizeof(x))
    #define FK(x) MEM(x,0)
    #define Tra(i,u) for(int i=G.st(u),v=G.to(i);~i;i=G.nx(i),v=G.to(i))
    #define p_b push_back
    #define sz(a) ((int)a.size())
    #define all(a) a.begin(),a.end()
    #define iter(a,p) (a.begin()+p)
    int I() {char c=getchar(); int x=0; int f=1; while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar(); while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return ((f==1)?x:-x);}
    template <typename T> void Rd(T& arg){arg=I();}
    template <typename T,typename...Types> void Rd(T& arg,Types&...args){arg=I(); Rd(args...);}
    void RA(int *p,int n) {F(i,1,n) *p=I(),++p;}
    struct edge{int u,v;} e[N];
    struct op{int t,x;} o[N];
    int val[N];
    int n,m,q;
    void Input()
    {
        Rd(n,m,q);
        F(i,1,n) val[i]=I();
        F(i,1,m) e[i]=(edge){I(),I()};
        F(i,1,q) o[i]=(op){I(),I()};
    }
    class Union_Find
    {
    public:
        int fa[N<<1],anc[N<<1];
        // anc用来路径压缩优化时间, fa保留原树的形状, 保证后面求dfs序的正确性
        // 然而这个trick很逊, 即使保持了原树的形状, 仍不能用于可撤销并查集 ———— 因为 anc 的修改太多了
        int tot=0;
        void clear(int n) {F(i,1,n+m) fa[i]=anc[i]=i; tot=n;}
        int  find(int x) {return x==anc[x]?x:anc[x]=find(anc[x]);}
        void merge(int u,int v)
        {
            u=find(u),v=find(v);
            if (u==v) return;
            ++tot; fa[u]=fa[v]=anc[u]=anc[v]=tot;
        }
    }un;
    int anc[N];
    class Graph
    {
    public:
        int head[N];
        struct node{int v,nx;} e[N]; int ecnt;
        void clear() {MEM(head,-1); MEM(e,-1); ecnt=-1;}
        void add(int u,int v)
        {
            e[++ecnt]=(node){v,head[u]}; head[u]=ecnt;
        }
        void add2(int u,int v) {add(u,v); add(v,u);}
        int st(int u) {return head[u];}
        int to(int i) {return e[i].v;}
        int nx(int i) {return e[i].nx;}
    }G;
    bool vis[N];
    int idfn[N],odfn[N],dfq[N],tick=0;
    void DFS(int u) // dfq 存储访问节点的顺序, 里面存节点, 换句话说就是 dfn 的逆变换
    {
        ++tick; dfq[tick]=u; idfn[u]=tick;
        Tra(i,u) DFS(v);
        odfn[u]=tick;
    }
    struct info{int pos,val;};
    info operator+(info a,info b)
    {
        return a.val>b.val?a:b;
    }
    class SegmentTree
    {
    public:
        info s[N<<2];
        #define ls ix<<1
        #define rs ix<<1|1
        #define inx int ix=1,int L=1,int R=n+m
        #define lson ls,L,mid
        #define rson rs,mid+1,R
        void up(int ix) {s[ix]=s[ls]+s[rs];}
        void Build(inx)
        {
            s[ix].val=val[dfq[L]]; s[ix].pos=L;
            if (L==R) return;
            int mid=(L+R)>>1;
            Build(lson); Build(rson); up(ix);
        }
        void Change(int pos,int val,inx)
        {
            if (L==R) {s[ix]=(info){pos,val}; return;}
            int mid=(L+R)>>1;
            if (pos<=mid) Change(pos,val,lson);
            else          Change(pos,val,rson);
            up(ix);
        }
        info Query(int l,int r,inx)
        {
            if (l<=L and R<=r) return s[ix];
            int mid=(L+R)>>1;
            if (r<=mid) return Query(l,r,lson);
            if (l>mid)  return Query(l,r,rson);
            return Query(l,mid,lson)+Query(mid+1,r,rson);
        }
    }T;
    void Soviet()
    {
        // 先删除所有边, 然后反向遍历, 维护Kruskal重构树
        // 途中预处理出每个查询节点的祖先(用当时的并查集做find), 同时把边加回来
        // 然后再利用Kruskal重构树的结构, 正着做一遍
        F(i,1,q) if (o[i].t==2) vis[o[i].x]=1;
        un.clear(n);
        F(i,1,m) if (!vis[i]) un.merge(e[i].u,e[i].v);
        D(i,q,1)
        {
            int t=o[i].t,x=o[i].x;
            if (t==2)
            {
                un.merge(e[x].u,e[x].v);
            }
            else
            {
                anc[i]=un.find(x);
            }
        }
        G.clear();
        F(i,1,un.tot) if (un.fa[i]!=i) G.add(un.fa[i],i);
        F(i,1,un.tot) if (un.find(i)==i) DFS(i);
        T.Build();
        F(i,1,q)
        {
            if (o[i].t==1)
            {
                int f=anc[i];
                info ans=T.Query(idfn[f],odfn[f]);
                printf("%d\n",ans.val);
                T.Change(ans.pos,0);
            }
        }
    }
    void IsMyWife()
    {
        Input();
        Soviet();
    }
}
#undef int //long long
int main()
{
    Flandre_Scarlet::IsMyWife();
    getchar();
    return 0;
}
CF1416D 做题心得的更多相关文章
- [JSOI2019]节日庆典 做题心得
		[JSOI2019]节日庆典 做题心得 一个性质有趣的字符串题 这要是在考场上我肯定做不出来吧 一开始还以为要 SAM 什么的暴力搞,没想到只用到了 \(Z\) 函数 -- 也是我生疏了罢 (学了啥忘 ... 
- [BJOI2016]水晶 做题心得
		[BJOI2016]水晶 做题心得 这是一个写了我两小时的傻逼题.写这个题浪费了一堆时间后,我才意识到我码力又不行了.于是整理起了实现技巧,开始练码力. 思路 不难.首先把 \((x,y,z)\) 变 ... 
- [NOIP补坑计划]NOIP2017 题解&做题心得
		终于做完了…… 场上预计得分:?(省一分数线:295) 由于看过部分题解所以没有预计得分qwq 题解: D1T1 小凯的疑惑 题面 震惊!一道小学奥数题竟难倒无数高中考生! 欢迎大家以各种姿势*和谐* ... 
- [NOIP补坑计划]NOIP2016 题解&做题心得
		感觉16年好难啊QAQ,两天的T2T3是不是都放反了啊…… 场上预计得分:100+80+100+100+65+100=545(省一分数线280) ps:loj没有部分分,部分分见洛咕 题解: D1T1 ... 
- [NOIP补坑计划]NOIP2015 题解&做题心得
		感觉从15年开始noip就变难了?(虽然自己都做出来了……) 场上预计得分:100+100+60~100+100+100+100=560~600(省一分数线365) 题解: D1T1 神奇的幻方 题面 ... 
- [NOIP补坑计划]NOIP2013 题解&做题心得
		场上预计得分:100+100+100+100+100+60=560(省一分数线410) 五道傻逼题+一道大搜索题…… 题解: D1T1 转圈游戏 题面 水题送温暖~ #include<algor ... 
- [NOIP补坑计划]NOIP2012 题解&做题心得
		场上预计得分:100+90+70+100+100+3060=490520(省一分数线245) 题解: D1T1 Vigenère 密码 题面 水题送温暖~~ #include<iostream& ... 
- [NOIP补坑计划]NOIP2014 题解&做题心得
		六道普及组题,没啥好说的 场上预计得分:100+100+100+100+100+100=600(省一分数线490) (AK是不可能AK的,这辈子不可能AK的) 题解: D1T1 生活大爆炸版石头剪刀布 ... 
- 标  题: [心得]传统IT转互联网面试经验分享
		发信人: lgonnet (逃之夭夭), 信区: Java标 题: [心得]传统IT转互联网面试经验分享发信站: 水木社区 (Wed Jul 1 10:18:38 2015), 站内 统一回复一下 ... 
随机推荐
- 容器编排系统K8s之访问控制--准入控制
			前文我们聊到了k8s的访问控制第二关RBAC授权插件的相关话题,回顾请参考:https://www.cnblogs.com/qiuhom-1874/p/14216634.html:今天我们来聊一下k8 ... 
- JAVA基础之this关键之理解
			突然觉得有几个知识点需要先复习一下 1.引用和对象并不一定要同时存在,可以只有引用,没有对象 :比如声明String a;如果非得提供一个比喻,可以用电视遥控器和电视来做比喻,遥控器比喻引用,电视 ... 
- SpringBoot+Prometheus+Grafana实现应用监控和报警
			一.背景 SpringBoot的应用监控方案比较多,SpringBoot+Prometheus+Grafana是目前比较常用的方案之一.它们三者之间的关系大概如下图: 关系图 二.开发SpringBo ... 
- Mono for android 访问Webservice和WebApi以及获取和解析JSON
			先看效果,注意:(1)这里由于我的模拟器不支持中文输入,所以,对于这张效果图,我是直接在代码中写死了我的查询城市,在下面的代码中我是没有把要查询的城市写死的. (2)读者要想成功使用本示例的所有代码的 ... 
- 单细胞分析实录(8): 展示marker基因的4种图形(一)
			今天的内容讲讲单细胞文章中经常出现的展示细胞marker的图:tsne/umap图.热图.堆叠小提琴图.气泡图,每个图我都会用两种方法绘制. 使用的数据来自文献:Single-cell transcr ... 
- 在mapper.xml映射文件中添加中文注释报错
			问题描述: 在写mapper.xml文件时,想给操作数据库语句添加一些中文注释,添加后运行报如下错误: 思考 可能是写了中文注释,编译器在解析xml文件时,未能成功转码,从而导致乱码.但是文件开头也采 ... 
- Sentinel限流之快速失败和漏桶算法
			距离上次总结Sentinel的滑动窗口算法已经有些时间了,原本想着一口气将它的core模块全部总结完,但是中间一懒就又松懈下来了,这几天在工作之余又重新整理了一下,在这里做一个学习总结. 上篇滑动窗口 ... 
- MySQL 集群知识点整理
			随着项目架构的不断扩大,单台 MySQL 已经不能满足需要了,所以需要搭建集群将前来的请求进行分流处理.博客主要根据丁奇老师的专栏<<MySQL实战45讲>>学习的总结. 架构 ... 
- idea中文注释出现乱码,我靠自己解决了
			如果你像我一样️,查遍google百度,半天下来还是找不到解决方案,说不定这篇博客能帮助你顺利解决呢 好了,那么开始说说我是怎么解决麻烦的. 首先,我想打开一份java文稿.光预览,它是没有任何问题的 ... 
- RandomForest 随机森林算法与模型参数的调优
			公号:码农充电站pro 主页:https://codeshellme.github.io 本篇文章来介绍随机森林(RandomForest)算法. 1,集成算法之 bagging 算法 在前边的文章& ... 
