[AHOI2022] 钥匙 简要题解

题面

题面

题解

考虑离线,计算每个钥匙的贡献,根据询问和树上点的 d f n dfn dfn 值加减贡献。
同种钥匙最多5把,考虑对每种颜色分别处理。
针对每种颜色,枚举钥匙的点 x x x ,从 x x x 出发dfs,计钥匙为 + 1 +1 +1 ,计宝箱为 − 1 -1 1 ,若到达某个点 y y y 时满足计数为 0 0 0 y y y 点为宝箱,则找到了一个钥匙和宝箱的匹配。dfs后可以找到每个钥匙的匹配,换句话说,从某个方向拿到钥匙,向某个方向走可以开启对应的宝箱,若询问的路径包含这条路径,则对答案有 1 1 1 的贡献。
x x x d f n dfn dfn 值为 d f n [ x ] dfn[x] dfn[x] x x x 子树内 d f n dfn dfn 值最大的点的 d f n dfn dfn 值为 e d [ x ] ed[x] ed[x] ,能拿到 x x x 的钥匙并开启 y y y 的宝箱记为有贡献,具体讨论 x x x y y y 的位置和贡献的关系:

  1. y y y x x x 的子树内,记 x x x y y y 的方向的儿子为 z z z ,则 [ 1 , d f n [ z ] − 1 ] ⋃ [ e d [ z ] + 1 , n ] [1,dfn[z]-1]\bigcup[ed[z]+1,n] [1,dfn[z]1][ed[z]+1,n]的点向 [ d f n [ y ] , e d [ y ] ] [dfn[y],ed[y]] [dfn[y],ed[y]] 的点走时,有贡献。
  2. x x x y y y 的子树内, 记 y y y x x x 方向的儿子为 z z z ,则 [ d f n [ x ] , e d [ x ] ] [dfn[x],ed[x]] [dfn[x],ed[x]] 的点向 [ 1 , d f n [ z ] − 1 ] ⋃ [ e d [ z ] + 1 , n ] [1,dfn[z]-1]\bigcup[ed[z]+1,n] [1,dfn[z]1][ed[z]+1,n] 的点走时,有贡献。
  3. 1 1 1 2 2 2 两点均不满足,则 x x x y y y 各在不同的子树内,则 [ d f n [ x ] , e d [ x ] ] [dfn[x],ed[x]] [dfn[x],ed[x]] 的点向 [ d f n [ y ] , e d [ y ] ] [dfn[y],ed[y]] [dfn[y],ed[y]] 的点走时,有贡献。

以上贡献可以由[出发区间,目标区间,贡献系数]的三元组表示,可以差分解决贡献的区间加,用树状树组维护前缀和。根据 d f n dfn dfn 值离线,从小到大处理差分和询问,即可以得到答案。
具体看一下代码就懂了:

#include<bits/stdc++.h>
#define ll long long
#define uit unsigned int
#define lowbit(x) (x&(-x))
#define pb push_back
using namespace std;
struct IO{
    static const int S=1<<21;
    char buf[S],*p1,*p2;int st[105],Top;
    ~IO(){clear();}
    inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
    inline void pc(const char c){Top==S&&(clear(),0);buf[Top++]=c;}
    inline char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
    inline IO&operator >> (char&x){while(x=gc(),x==' '||x=='\n'||x=='\r');return *this;}
    template<typename T>inline IO&operator >> (T&x){
        x=0;bool f=0;char ch=gc();
       while(!isdigit(ch)){if(ch=='-') f^=1;ch=gc();}
        while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
        f?x=-x:0;return *this;
    }
    inline IO&operator << (const char c){pc(c);return *this;}
    template<typename T>inline IO&operator << (T x){
        if(x<0) pc('-'),x=-x;
        do{st[++st[0]]=x%10,x/=10;}while(x);
        while(st[0]) pc('0'+st[st[0]--]);return *this;
    }
}fin,fout;
const int N=5e5+10,M=1e6+5,MAXP=1e7+1e6+10;
int n,m,pcnt=0,dfc=0,idx,colnow,type[N],col[N],dep[N],f[N][25],tr[N],dfn[N],ed[N],stk[N],top=0,seq[N],dp[N],res[M];
vector<int> e[N],vit[N],colbin[N];
struct P{
    int dfctim,l,r,op;//出发点边界dfn,目标区间,操作+/- or 询问
    // op=0 -> 区间+  op=-1 -> 区间-  op>0 -> 询问编号
    friend bool operator<(P x,P y){
        if(x.dfctim!=y.dfctim) return x.dfctim<y.dfctim;
        return x.op<y.op;
    }
}p[MAXP];//操作和询问
void Add(int x,int y){//树状树组函数
    for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=y;
}
int Sum(int x){//树状树组函数
    int temp=0;
    for(int i=x;i;i-=lowbit(i)) temp+=tr[i];
    return temp;
}
void dfs(int x,int fa){//预处理dfn等信息
    dep[x]=dep[fa]+1;dfn[x]=ed[x]=++dfc;
    for(int y:e[x]){
        if(y==fa) continue;
        f[y][0]=x;
        for(int j=1;j<=idx;j++){
            f[y][j]=f[f[y][j-1]][j-1];
        }
        dfs(y,x);
        ed[x]=ed[y];
    }
}
int lca(int x,int y){
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=idx;i>=0;i--) if(f[x][i]&&dep[f[x][i]]>=dep[y]) x=f[x][i];
    if(x==y) return x;
    for(int i=idx;i>=0;i--) if(f[x][i]&&f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}
int last(int rt,int y){//rt向y的方向的儿子
    for(int i=idx;i>=0;i--) if(f[y][i]&&dep[f[y][i]]>dep[rt]) y=f[y][i];
    return y;
}
bool sort_by_dfn(int x,int y){
    return dfn[x]<dfn[y];
}
void ins(int x){//建虚树
    if(!top){
        stk[top=1]=x;
        return ;
    }
    int A=lca(stk[top],x);
    while(top>1&&dep[A]<dep[stk[top-1]]){
        vit[stk[top-1]].pb(stk[top]);
        vit[stk[top]].pb(stk[top-1]);
        top--;
    }
    if(dep[A]<dep[stk[top]]){
        vit[A].pb(stk[top]);
        vit[stk[top]].pb(A);
        top--;
    }
    if(!top||stk[top]!=A) stk[++top]=A;
    stk[++top]=x;
}
bool intree(int rt,int y){//y是否在rt的子树内
    return dfn[rt]<=dfn[y]&&ed[y]<=ed[rt];
}
void report(int x,int y){//根据在树上的位置关系,处理操作,差分贡献
    int z;
    if(intree(x,y)){
        z=last(x,y);
        p[++pcnt]={1,dfn[y],ed[y],0};
        p[++pcnt]={dfn[z],dfn[y],ed[y],-1};
        p[++pcnt]={ed[z]+1,dfn[y],ed[y],0};
    }
    else if(intree(y,x)){
        z=last(y,x);
        p[++pcnt]={dfn[x],1,dfn[z]-1,0};
        p[++pcnt]={dfn[x],ed[z]+1,n,0};
        p[++pcnt]={ed[x]+1,1,dfn[z]-1,-1};
        p[++pcnt]={ed[x]+1,ed[z]+1,n,-1};
    }
    else{
       p[++pcnt]={dfn[x],dfn[y],ed[y],0};
       p[++pcnt]={ed[x]+1,dfn[y],ed[y],-1};
    }
}
void match(int x,int fa,int rt){
    dp[x]=dp[fa]+(col[x]==colnow?(type[x]==1?1:-1):0);
    if(col[x]==colnow&&type[x]==2&&dp[x]==0){//找到匹配的宝箱就停下
        report(rt,x);
        return;
    }
    for(int y:vit[x]){
        if(y==fa) continue;
        match(y,x,rt);
    }
}
void clearvit(int x,int fa){
    for(int y:vit[x]){
        if(y==fa) continue;
        clearvit(y,x);
    }
    vit[x].clear();
}
void create(int R){
    top=0;colnow=R;int len=0;
    for(int i=0;i<colbin[R].size();i++){//建虚树
        seq[++len]=colbin[R][i];
    }
    sort(seq+1,seq+len+1,sort_by_dfn);
    if(seq[1]!=1) stk[top=1]=1;
    for(int i=1;i<=len;i++) ins(seq[i]);
    while(top>1){
        vit[stk[top]].pb(stk[top-1]);
        vit[stk[top-1]].pb(stk[top]);
        top--;
    }
    top=0;
    for(int i=1;i<=len;i++){
        if(type[seq[i]]==1){
            match(seq[i],0,seq[i]);//匹配钥匙和宝箱
        }
    }
    clearvit(seq[1],0);//初始化
}
int main(){
    freopen("keys.in","r",stdin);
    freopen("keys.out","w",stdout);
    fin>>n>>m;idx=log(n)/log(2)+1;
    for(int i=1;i<=n;i++){
        fin>>type[i]>>col[i];
        colbin[col[i]].pb(i);//存到对应的颜色的桶
    }
    for(int i=1,u,v;i<n;i++){
        fin>>u>>v;
        e[u].pb(v);//建原边
        e[v].pb(u);
    }
    dfs(1,0);//预处理dfn等信息
    for(int i=1,s,e;i<=m;i++){
        fin>>s>>e;
        p[++pcnt]={dfn[s],dfn[e],0,i};//询问
    }
    for(int i=1;i<=n;i++){
        if(colbin[i].empty()) continue;
        create(i);//对每个颜色分别处理
    }
    sort(p+1,p+pcnt+1);//按dfn序、操作与询问的优先级排序
    for(int i=1;i<=pcnt;i++){
        if(p[i].op==0){//区间+
            Add(p[i].l,1);
            Add(p[i].r+1,-1);
        }
        else if(p[i].op==-1){//区间-
            Add(p[i].l,-1);
            Add(p[i].r+1,1);
        }
        else{
            res[p[i].op]=Sum(p[i].l);//询问答案
        }
    }
    for(int i=1;i<=m;i++) fout<<res[i],fout.pc('\n');//输出
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值