首页 > 其他分享 >[NOI2021] 轻重边

[NOI2021] 轻重边

时间:2024-11-13 19:09:04浏览次数:1  
标签:int mid NOI2021 tor 100001 now id 轻重

气死我了这题,还是写一下题解

首先有一个非常好的转化,你可以把给定操作转为树上颜色问题

假设将操作 \(1\) 改成 “将从 \(x\) 到 \(y\) 路径上的所有点都涂上一种新的颜色”,那么可以发现,与路径上的点相邻的所有非路径点,与路径上的点颜色必然不同,路径上的点之间两两必然相同

因此就可以定义重边为 “端点颜色相同的边”,轻边为 “端点颜色不同的边”,这样就可以将原问题转为树上数颜色问题

转化到这里就很好做了,由于数颜色是合并连通块,可以直接树剖之后放到线段树上去做,维护当前连续段长度,左端点,右端点,然后每次尝试在中间合并(合并最多只会增加 \(1\) 的贡献)

操作要求我们支持区间修改与区间查询

对于区间修改,修改后整体覆盖的区间,其连通块个数应为 \(len-1\) 而不是 \(len\),因为我们求的是联通的边数而不是点数

对于区间查询,同样维护长度,左端点与右端点,询问时分治并尝试在中间合并

然后是这个树剖的查询操作,这里不能无脑跳,因为我们需要合并连续段,因此必须知道当前链哪端是左边哪端是右边,这个可以通过维护一个变量来记录当前跳的是左边点还是右边点,然后通过深度低的点更靠左来判断左右端点

这个题需要特判颜色初值 \(0\),因为 \(0\) 和 \(0\) 不能被称为颜色相等的点(初始所有点都是轻边)

因为懒得特判所以为点赋了不同初值,没想到最后发现和树剖的初值在值域上有交

#include<bits/stdc++.h>
using namespace std;
#define clearzero(x) memset((x),0,sizeof(x))
struct tree{
    int lc,rc;
    int lazy;
    int tot;
}t[100001*4];
int wnew[100001];
#define tol (id*2)
#define tor (id*2+1)
#define mid(l,r) mid=((l)+(r))/2
void pushdown(int id,int l,int r){
    if(t[id].lazy){
        int mid(l,r);
        t[tol]={t[id].lazy,t[id].lazy,t[id].lazy,mid-l};
        t[tor]={t[id].lazy,t[id].lazy,t[id].lazy,r-(mid+1)};
        t[id].lazy=0;
    }
}
void update(int id){
    t[id].lc=t[tol].lc;
    t[id].rc=t[tor].rc;
    t[id].tot=t[tol].tot+t[tor].tot+(t[tol].rc==t[tor].lc);
}
void build(int id,int l,int r){
    if(l==r){
        t[id]={-wnew[l],-wnew[l],0,0};
        return;
    }
    int mid(l,r);
    build(tol,l,mid);
    build(tor,mid+1,r);
    update(id);
}
void change(int id,int l,int r,int L,int R,int val){
    if(L<=l and r<=R){
        t[id]={val,val,val,r-l};
        return;
    }
    pushdown(id,l,r);
    int mid(l,r);
    if(R<=mid) change(tol,l,mid,L,R,val);
    else if(L>=mid+1) change(tor,mid+1,r,L,R,val);
    else{
        change(tol,l,mid,L,mid,val);
        change(tor,mid+1,r,mid+1,R,val);
    }
    update(id);
}
tree ask(int id,int l,int r,int L,int R){
    if(L<=l and r<=R){
        return t[id];
    }
    pushdown(id,l,r);
    int mid(l,r);
    if(R<=mid) return ask(tol,l,mid,L,R);
    else if(L>=mid+1) return ask(tor,mid+1,r,L,R);
    tree a=ask(tol,l,mid,L,mid),b=ask(tor,mid+1,r,mid+1,R);
    return {a.lc,b.rc,0,a.tot+b.tot+(a.rc==b.lc)};
}
int n,m;
vector<int>e[100001];
int deep[100001],fa[100001];
int size[100001],maxson[100001];
int id[100001],top[100001],idcnt;
int dfn[100001],dfncnt;
void dfs1(int now,int last){
    fa[now]=last;
    deep[now]=deep[last]+1;
    dfn[last]=++dfncnt;
    size[now]=1;
    int maxsonsize=0;
    for(int i:e[now]){
        if(i!=last){
            dfs1(i,now);
            size[now]+=size[i];
            if(size[i]>maxsonsize){
                maxsonsize=size[i];
                maxson[now]=i;
            }
        }
    }
}
void dfs2(int now,int nowtop){
    top[now]=nowtop;
    id[now]=++idcnt;
    if(!maxson[now]) return;
    dfs2(maxson[now],nowtop);
    for(int i:e[now]){
        if(i!=fa[now] and i!=maxson[now]){
            dfs2(i,i);
        }
    }
}
void change_path(int x,int y,int val){
    while(top[x]!=top[y]){
        if(deep[top[x]]<deep[top[y]]) swap(x,y);
        change(1,1,n,id[top[x]],id[x],val);
        x=fa[top[x]];
    }
    if(deep[x]<deep[y]) swap(x,y);
    change(1,1,n,id[y],id[x],val);
}
int ask_path(int x,int y){
    tree ans1={-1-2*n,-2-2*n,0,0},ans2={-3-2*n,-4-2*n,0,0};
    bool isans1=true;
    while(top[x]!=top[y]){
        if(deep[top[x]]<deep[top[y]]){
            isans1^=1;
            swap(x,y);
        }
        tree res=ask(1,1,n,id[top[x]],id[x]);
        if(isans1){
            ans1={ans1.lc,res.lc,0,ans1.tot+res.tot+(ans1.rc==res.rc)};
        }
        else{
            ans2={res.lc,ans2.rc,0,ans2.tot+res.tot+(ans2.lc==res.rc)};
        }
        x=fa[top[x]];
    }
    if(deep[x]<deep[y]){
        isans1^=1;
        swap(x,y);
    }
    tree res=ask(1,1,n,id[y],id[x]);
    if(isans1){
        ans1={ans1.lc,res.lc,0,ans1.tot+res.tot+(ans1.rc==res.rc)};
    }
    else{
        ans2={res.lc,ans2.rc,0,ans2.tot+res.tot+(ans2.lc==res.rc)};
    }
    return ans1.tot+ans2.tot+(ans1.rc==ans2.lc);
}
void clear(){
    idcnt=0;
    dfncnt=0;
    clearzero(t);
    clearzero(wnew);
    clearzero(id);
    clearzero(fa);
    clearzero(deep);
    clearzero(size);
    clearzero(maxson);
    clearzero(top);
    clearzero(dfn);
    for(int i=1;i<=n;++i){
        e[i].clear();
    }
}
int main(){
    int t;cin>>t;
    while(t--){
        cin>>n>>m;
        clear();
        for(int i=1;i<=n-1;++i){
            int x,y;
            cin>>x>>y;
            e[x].push_back(y);
            e[y].push_back(x);
        }
        dfs1(1,0);
        dfs2(1,1);
        iota(wnew+1,wnew+n+1,1);
        build(1,1,n);
        for(int i=1;i<=m;++i){
            int opt,x,y;
            cin>>opt>>x>>y;
            if(opt==1){
                change_path(x,y,i);
            }
            else{
                cout<<ask_path(x,y)<<'\n';
            }
        }
    }
}

标签:int,mid,NOI2021,tor,100001,now,id,轻重
From: https://www.cnblogs.com/HaneDaCafe/p/18544573

相关文章

  • 「NOI2021 D1T3 庆典」题解
    uoj675加强:\(\sumk\le6\times10^5\)暴力\(u\)在\(s\Rightarrowt\)路径上\(\iff\)正图上\(s\Rightarrowu\)且反图上\(u\Rightarrowt\)时间复杂度\(O((n+m)q)\)正解只关心可达性,不妨SCC缩点成DAG。注意到一个奇怪的条件:对于三座城市\(x,y,z\),若\(x\Right......
  • Luogu7740 [NOI2021]机器人游戏 做题记录
    link一道炸裂的题目。首先样例解释已经告诉我们可以容斥。考虑枚举可行的位置集合\(S\),我们需要统计\(\forallp\inS\),纸条初始状态和目标状态都相同的方案数。显然每个机器人独立,可以分开考虑。对于一个机器人,他的行动对纸条的每个格子要么赋值为\(0/1\),要么不变,要么取......
  • NOI2021 Day1
    轻重边把询问和修改都转到点上考虑。相当于给某些路径上的点都染上一种未出现过的颜色,然后查询某些路径上颜色相同的相邻点对数。注意初始时所有点的颜色应该互不相同树剖+线段树就做完了。需要特别注意的是树剖跳链时也会产生贡献。时间复杂度\(\mathcalO(n\log^2n)\)......
  • P7739 [NOI2021] 密码箱
    题意:Yelekastee是U国著名的考古学家。在最近的一次考古行动中,他发掘出了一个远古时期的密码箱。经过周密而严谨的考证,Yelekastee得知密码箱的密码和某一个数列\(\{a_n\}\)相关。数列\(\{a_n\}\)可以用如下方式构造出来:初始时数列长度为\(2\)且有\(a_0=0,a_1......
  • NOI2021 轻重边 题解
    NOI2021轻重边题目链接:#4812.[NOI2021]轻重边前置知识:#树链剖分#线段树题目大意给定\(n\)个点组成的树,\(m\)次操作。修改操作会让路径\(a\tob\)上的所有点的所有连边对答案的贡献变为\(0\),路径\(a\tob\)的所有边的贡献变为\(1\);查询操作则统计路径\(a\tob\)的......
  • P8112 [Cnoi2021] 符文破译 题解
    题目传送门思路先看数据范围,我们发现两个字符串的长度最大会达到\(5\times10^7\)。这立刻打消了我用暴力的想法。于是,我选择了用KMP模式匹配,这一个能够在线性时间内判定字符串\(A\)是否是字符串\(B\)的字串,并求出字符串\(A\)在字符串\(B\)中各次出现的位置。如......
  • P7735 [NOI2021] 轻重边 题解
    是一道树剖好题,之前听lsl讲过一点,于是很快就做出来了。题意:有一个\(n\)个节点的树,最开始的时候所有边都是轻边,维护两个操作:操作一:将\(u\)到\(v\)的路径中经过的所有点的邻边变为轻边,再将这条路径上的边变为重边。操作二:求出\(u\)到\(v\)这条路径上有多少条重边......
  • P8111 [Cnoi2021] 区间
    [Cnoi2021]区间LuoguP8111题目背景Cirno有一个区间\([a,b](1\lea\leb\len)\),而你的任务是在规定的次数内帮Rumia猜出这个区间。每次,你可向Cirno询问一个数字\(k\),而Cirno会告诉你这个数字与区间\([a,b]\)的关系。题目描述为了猜到这个区间,你需要实现一个函......
  • NOI2021 路径交点
    洛谷传送门LOJ传送门两条路径的交点数量只和起点数量有关。容易发现是终点排列的逆序对数的奇偶性。求一个\(f_{i,j}\)表示从第\(1\)层的第\(i\)个点到第\(k\)层的第\(j\)个点的路径数量,对这个矩阵求行列式即可。对于相交的路径数不用考虑,因为总存在和它对应的一条......
  • 浅谈树链剖分—轻重链剖分
    闲话似乎会有很多种树剖,什么长链剖分之类的,但是暂时只会轻重链剖分(可怜)。以前的版本在这里,但是感觉写的太粗糙了,所以决定重写一篇(我也不知道为什么要一直写树剖而不写点别的)。正文引入如果给出一个序列,有一些区间修改查询之类的操作,直接上线段树什么的就完事了,但是如果给出的......