首页 > 其他分享 >P4899 [IOI2018] werewolf 狼人 题解

P4899 [IOI2018] werewolf 狼人 题解

时间:2023-10-18 13:14:37浏览次数:39  
标签:重构 后半程 题解 P4899 狼人 dfn 半程 Now Kruskal

P4899 [IOI2018] werewolf 狼人 题解

题目描述

省流:

\(n\) 个点,\(m\) 条边,\(q\) 次询问,对于每一次询问,给定一个起点 \(S\) 和终点 \(T\) ,能否找到一条路径,前半程不能走 \(0\thicksim L-1\) 这些点,后半程不能走 \(R+1\thicksim N-1\) 这些点。中途必须有一个点在\(L\thicksim R\)之间。

题目分析

首先对于这种限定了走的边的属性,或者走的点的属性的路径题,自然想到Kruskal重构树,然后注意到城市从 \(0\) 开始标号很可恶,所以我们就可以将所有标号加一,并且转化题意,对于前半段,我们只走 \(L\thicksim N\) 这些点,对于后半程,我们只走 $1\thicksim R $ 这些点,那么对于这样的要求,我们就可以建立Kruskal重构树了。首先分析边的权值,我们知道,走了一条边,就意味着会经过这条边连接的两个端点,所以说对于前半段来说,我们可以以\(min(x,y)\)为边权,从大到小排序建立一棵Kruskal重构树。因为我们对一条边所定的限制,就是尽量走大的编号,而界定前半程能否走这一条边的限制,即是会不会走到因为太小而不合法的编号,而对于后半程来说,同理可得,以\(max(x,y)\)为边权,从小到大排序建立一棵Kruskal重构树。

那么建立好重构树之后,我们就可以将路程分为三段,前半程,转折点,后半程,那么前半程和后半程都必须可达这个转折点,所以合法的转折点也就是前半程可达的点和后半程可达的点的交集。那么我们可以在前半程的Kruskal重构树上跳到权值大于等于\(L\)的最浅的结点,那么前半程可达的点一定在该点的子节点之中,对于后半程同理,于是我们就将问题转化成了求这样两个节点囊括的子节点有没有交集。

对于这样的问题,我们可以使用主席树解决。我们都知道一个点的编号与其\(dfn\)序是一一对应的,所以我们暂时可以用\(dfn\)序代替点的标号,我们在两棵树上先跑一个\(dfn\)序,然后按照前半程树的\(dfn\)序来将后半程的\(dfn\)加入主席树。当我们对于一个查询的时候,我们只需要先将前半程对应的祖先的所囊括的子节点的\(dfn\)序的左右端点作为查询的左右两数,让后查询是否存在值在所询问的后半程对应的祖先的\(dfn\)序的左右端点之间,如果存在,说明有一个点是它们的交集。(这段确实不好理解)

代码部分(因为有两个重构树,所以写的Class)

/*
 * Author:Ehundategh
 * Update:2023/10/17
 * Title:P4899 [IOI2018] werewolf 狼人.cpp
 * You steal,I kill
 */
#include <cstdio>
#include <iostream>
#include <algorithm>
#define MAXN 800010
#define MAXM 800010
#define LSon Node[Now].LeftS
#define RSon Node[Now].RightS
using namespace std;
int n,m,q,In1,In2,In3,In4;
template <typename T> inline void read(T &x){
    int f=0;x=0;char c=getchar();
    for(;!isdigit(c);c=getchar())f|=(c=='-');
    for(;isdigit(c);c=getchar())x=((x<<3)+(x<<1)+(c^48));
    x=f?-x:x;
}
struct edge{
    int St,Ed;
}Edge[MAXM];
bool cmpman(edge a,edge b){return min(a.St,a.Ed)>min(b.St,b.Ed);}
bool cmpwolf(edge a,edge b){return max(a.St,a.Ed)<max(b.St,b.Ed);}
class President_Tree{
private:
    int cnt=0;
    struct node{
        int Left,Right;
        int LeftS,RightS;
        int Sum;
    }T[MAXN<<5];
public:
    int Roots[MAXN];
    int New_Tree(int Last,int Left,int Right,int Value){
        int Root=++cnt;
        T[Root].LeftS=T[Last].LeftS;
        T[Root].RightS=T[Last].RightS;
        T[Root].Sum=T[Last].Sum+1;
        T[Root].Left=Left;T[Root].Right=Right;
        int Mid=(Left+Right)/2;
        if(Left!=Right){
            if(Value<=Mid){T[Root].LeftS=New_Tree(T[Last].LeftS,Left,Mid,Value);}
            else{T[Root].RightS=New_Tree(T[Last].RightS,Mid+1,Right,Value);}
        }
        return Root;
    }
    int Query(int Pl,int Pr,int Left,int Right){
        if(T[Pr].Left>=Left&&T[Pr].Right<=Right){
            return T[Pr].Sum-T[Pl].Sum;
        }
        else if(T[Pr].Right<Left||T[Pr].Left>Right) return 0;
        else return (Query(T[Pl].LeftS,T[Pr].LeftS,Left,Right)|Query(T[Pl].RightS,T[Pr].RightS,Left,Right));
    }
}President;
class Kruskal{
private:
    int Fa[MAXN<<1][21],Father[MAXN<<1];
public:
    struct node{
        int LeftS,RightS,Left,Right,Value;
    }Node[MAXN<<1];
    int cnt,DFN[MAXN],cnd=0,Line[MAXN];
    int Find(int a){return Father[a]==a?Father[a]:Father[a]=Find(Father[a]);}
    void Merge(int a,int b,int c,int Type){
        int Faa=Find(a),Fab=Find(b);
        Father[Faa]=Father[Fab]=c;
        Fa[Faa][0]=Fa[Fab][0]=c;
        Node[c].LeftS=Faa;Node[c].RightS=Fab;
        if(Type==0) Node[c].Value=min(a,b);
        else Node[c].Value=max(a,b);
    }
    void Build(int Type){
        cnt=n;
        for(int i=1;i<=2*n;i++) Father[i]=i,Fa[i][0]=i;
        if(Type==0) sort(Edge+1,Edge+m+1,cmpman);
        else sort(Edge+1,Edge+m+1,cmpwolf);
        for(int i=1;i<=m;i++){
            if(Find(Edge[i].St)==Find(Edge[i].Ed)) continue;
            Merge(Edge[i].St,Edge[i].Ed,++cnt,Type);
        }
    }
    void Pre(){
        for(int i=1;i<=cnt;i++){
            if(Fa[i][0]==i) DFS(i);
        }
    }
    void DFS(int Now){
        Node[Now].Left=1<<30;
        Node[Now].Right=0;
        if(!(LSon||RSon)) {
            DFN[Now]=Node[Now].Left=Node[Now].Right=++cnd;
            Line[DFN[Now]]=Now;
            return;
        }
        DFS(LSon),Node[Now].Left=min(Node[Now].Left,Node[LSon].Left),Node[Now].Right=max(Node[Now].Right,Node[LSon].Right);
        DFS(RSon),Node[Now].Left=min(Node[Now].Left,Node[RSon].Left),Node[Now].Right=max(Node[Now].Right,Node[RSon].Right);
    }
    void Init(){
        for(int i=1;i<=19;i++){
            for(int j=1;j<=cnt;j++){
                Fa[j][i]=Fa[Fa[j][i-1]][i-1];
            }
        }
    }
    int Jump(int Now,int Type,int Top){
        for(int i=19;i>=0;i--){
            if(Type==0&&Node[Fa[Now][i]].Value>=Top) Now=Fa[Now][i];
            else if(Type==1&&Node[Fa[Now][i]].Value<=Top) Now=Fa[Now][i];
        }
        return Now;
    }
}T1,T2;
void Init_President(){
    for(int i=1;i<=n;i++){
        President.Roots[i]=President.New_Tree(President.Roots[i-1],1,n,T2.DFN[T1.Line[i]]);
//        printf("%d %d\n",i,T2.DFN[T1.Line[i]]);
    }
}
bool Judge(int S,int T,int L,int R){
    S=T1.Jump(S,0,L);
    T=T2.Jump(T,1,R);
//    printf("%d %d %d %d\n",T1.Node[S].Left-1,T1.Node[S].Right,T2.Node[T].Left,T2.Node[T].Right);
    int Temp=President.Query(President.Roots[T1.Node[S].Left-1],President.Roots[T1.Node[S].Right],T2.Node[T].Left,T2.Node[T].Right);
    if(Temp) return true;
    else return false;
}
int main(){
    read(n);read(m);read(q);
    for(int i=1;i<=m;i++){
        read(Edge[i].St);read(Edge[i].Ed);
        Edge[i].St++;
        Edge[i].Ed++;
    }
    T1.Build(0);T2.Build(1);
    T1.Pre();T2.Pre();
    T1.Init();T2.Init();
    Init_President();
    while(q-->0){
        read(In1);read(In2);read(In3);read(In4);
        In1++;In2++;In3++;In4++;
        if(Judge(In1,In2,In3,In4)) puts("1");
        else puts("0");
    }
    return 0;
}

如果觉得这篇题解让你有所收获,就点个赞吧。

标签:重构,后半程,题解,P4899,狼人,dfn,半程,Now,Kruskal
From: https://www.cnblogs.com/Ehundategh/p/17771825.html

相关文章

  • AT_abc134_d Preparing Boxes题解
    简述题意这什么破翻译,看了AtCoder的英文才看懂。给定一个长度为\(n\)序列\(a\),要求构造一个数列\(b\),使得对于任意\(i\),满足:\(1\lei\len\)将\(b\)序列下标为\(i\)的倍数的值相加使得这个总和模2等于\(a_i\)。求序列\(b\)中值为1的个数与值为1......
  • P9700题解
    思路看数据范围,发现范围很小,直接用搜索。搜索时枚举每个点,如果有棋子就枚举方向,如果这个方向合法,则将剩余棋子数减一,继续搜索。搜索时参数只需要传当前棋子数就行了。有以下几点需要注意多组数据每次需要初始化。判断是否合法时要注意。每次记得回溯棋子。ACCODE......
  • SP26719题解
    考虑动态规划。思路设\(dp_{i,j}\)为\((1,1)\)到\((i,j)\)的方案数,而如果要到这个点,肯定是从左边和上边来。所以递推公式为:\(dp_{i,j}=dp_{i,j-1}+dp_{i-1,j}\)。预处理:将横或纵坐标为1的点赋值为1,因为到达这些点的只有一种方法。注意:每次需要清零数组。ACC......
  • CF1873B题解
    这题其实可以数学方法差小积大解决。差越小积越大,那肯定是让最小的数加一啦。将所有数的积除以最小值再乘上最小值加一。#include<bits/stdc++.h>usingnamespacestd;signedmain(){ intT; cin>>T; while(T--){ longlongcnt=0,n,a[10],minn=LONG_LONG_MAX,ans=1; c......
  • CF1868C Travel Plan 题解
    原题翻译发现所有长度相同的简单路径的权值可能情况相同,且最长的简单路径长度为\(O(\logn)\)级别,考虑维护所有长度的简单路径在一棵树上出现的次数,每种简单路径的权值在所有树上出现的次数,相乘即使答案。我们考虑长度为\(x\)的路径对答案的贡献,考虑枚举这条路径的贡献\(......
  • 【前缀和优化 dp】CF1542E2 Abnormal Permutation Pairs (hard version) 题解
    CF1542E2首先时间复杂度肯定是\(\mathcal{O}(n^3)\)的。容易想到先枚举最长公共前缀,然后枚举\(p_{len+1}\)和\(q_{len+1}\),再枚举逆序对数进行统计。令\(f_{i,j}\)表示有\(j\)个逆序对的\(i\)阶排列的个数。易得转移\(f_{i,j}=\sum\limits_{k=\max(j-i+1,0)}^{j}f......
  • 【根号分治】P9212 「蓬莱人形」 题解
    P9212看到除法相关容易想到根号分治。先对\(x,y\)进行讨论,不妨令\(0\lex,y<m\)。\(x<y\)时,当满足\(a_i+y<m\)或\(a_i+x\gem\)时,即当\(a_i<m-y\)或\(a_i\gem-x\)满足\((a_i+x)\bmodm<(a_i+y)\bmodm\),即\(a_i\bmodm\in[0,m-y-1]\bigcup[m-x,m......
  • 【dp】【竞赛图的性质】ARC163D Sum of SCC 题解
    ARC163D发现这个竞赛图一定能被分为两个集合\(A\),\(B\)。满足\(\forallu\inA,v\inB\),均有\(u\tov\inE\)。答案就是划分这两个集合的方案数。证明:首先,竞赛图缩完点后一定是一条链,对强连通分量进行标号,满足编号小的强连通分量指向编号大的强连通分量。不妨令竞赛图\(G\)......
  • 【dp】【进制】P3464 [POI2007] WAG-Quaternary Balance 题解
    P3464显然的,先将原数变为四进制的数。由于算的是进位/不进位的代价最小值和方案数,容易想到dp。这里假定该四进制数是从高位到低位的,顺序显然是由低位到高位。令\(f_{i,0/1}\)表示第\(i\)位进/不进位的最小代价,\(g_{i,0/1}\)表示的是最小代价下的方案数。转移是简单的......
  • [题解] CF1790E - XOR Tree
    CF1790E-XORTree题意给定一颗无根树,在可以改变任意一个点的点权操作基础上,让树上任意简单路径的异或和不为\(0\),问最少需要多少次操作。思路假设某个点为根,设\(pre_x\)为\(x\)点到根的树上前缀异或和,\(a_x\)为\(x\)的点权,则\(x\)和\(y\)之间简单路径的异或和......