[IOI2018]werewolf狼人——kruskal重构树+可持久化线段树

题目链接:

IOI2018werewolf

题目中编号都是从0开始,太不舒服了,我们按编号从1开始讲QAQ。

题目大意就是询问每次从一个点开始走只能走编号在[l,n]中的点,在任意点变成狼,之后只能走[0,r]中的点,是否能到达另一个点。

后一部分其实就是找有哪些点只走[0,r]中的点能到达终点,那么反过来看,就是终点只走[0,r]中的点能到达哪些点。

那么只要起点能到达的点和终点能到达的点中有交集就有解。

因为起点只能走一些编号较大的点,那么我们求出原图的最大生成树,建出kruskal重构树,求出重构树的dfs序,每次询问在重构树上倍增找能到达的联通块在dfs序上的区间就好了。

相反,从终点走就求出原图的最小生成树,然后也按上述方法找。

找到两段dfs序区间后只要这两段区间中有相同点就能判有解。

我们将每个点在第一个dfs序中的位置作为横坐标,在第二个dfs序中的位置作为纵坐标,剩下的就是一个简单的二维数点问题了。

等会,上面是不是差点什么?原图没有边权啊?

我们以最大生成树为例,对于一条边(x,y),两个点能否通过这条边互相到达,由这两个点中较小的点决定,因此求最大生成树时每条边边权就是边两端点中较小的那个。最小生成树边权就是两端点中较大的那个。

整体思路就是分别建出原图最小生成树和最大生成树的重构树,分别求出每个点在两个重构树中的dfs序位置,然后可持久化线段树二维数点。

kruskal重构树在这里就不赘述了,如果不是太了解可以参考我的另一篇博客NOI2018归程,那道题和这道题思想很像。

题面似乎没说保证整张图联通,因此可能是kruskal重构森林。

#include<map>
#include<set>
#include<stack>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<bitset>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
struct miku
{
    int u,v,x,y;
}a[400010];
int cnt;
int n,m,k;
int s,t,l,r;
int num1,num2;
int sum1,sum2;
int s1[400010];
int s2[400010];
int t1[400010];
int t2[400010];
int q1[400010];
int q2[400010];
int v1[400010];
int v2[400010];
int fa1[400010];
int fa2[400010];
int ls1[400010];
int rs1[400010];
int ls2[400010];
int rs2[400010];
int ls[5000010];
int rs[5000010];
int vis1[400010];
int vis2[400010];
int sum[5000010];
int root[200010];
int f1[400010][18];
int f2[400010][18];
int find1(int x)
{
    if(fa1[x]==x)
    {
        return x;
    }
    return fa1[x]=find1(fa1[x]);
}
int find2(int x)
{
    if(fa2[x]==x)
    {
        return x;
    }
    return fa2[x]=find2(fa2[x]);
}
bool cmp1(miku a,miku b)
{
    return a.x>b.x;
}
bool cmp2(miku a,miku b)
{
    return a.y<b.y;
}
void build(int &rt,int l,int r)
{
    rt=++cnt;
    if(l==r)
    {
        return ;
    }
    int mid=(l+r)>>1;
    build(ls[rt],l,mid);
    build(rs[rt],mid+1,r);
}
void updata(int &rt,int pre,int l,int r,int k)
{
    rt=++cnt;
    sum[rt]=sum[pre]+1;
    if(l==r)
    {
        return;
    }
    ls[rt]=ls[pre];
    rs[rt]=rs[pre];
    int mid=(l+r)>>1;
    if(k<=mid)
    {
        updata(ls[rt],ls[pre],l,mid,k);
    }
    else
    {
        updata(rs[rt],rs[pre],mid+1,r,k);
    }
}
int query(int x,int y,int l,int r,int L,int R)
{
    if(L<=l&&r<=R)
    {
        return sum[y]-sum[x];
    }
    int mid=(l+r)>>1;
    int res=0;
    if(L<=mid)
    {
        res+=query(ls[x],ls[y],l,mid,L,R);
    }
    if(R>mid)
    {
        res+=query(rs[x],rs[y],mid+1,r,L,R);
    }
    return res;
}
void dfs1(int x)
{
    vis1[x]=1;
    s1[x]=sum1;
    if(x<=n)
    {
        q1[++sum1]=x;
    }
    for(int i=1;i<=17;i++)
    {
        f1[x][i]=f1[f1[x][i-1]][i-1];
    }
    if(ls1[x])
    {
        dfs1(ls1[x]);
    }
    if(rs1[x])
    {
        dfs1(rs1[x]);
    }
    t1[x]=sum1;
}
void dfs2(int x)
{
    vis2[x]=1;
    s2[x]=sum2;
    if(x<=n)
    {
        q2[++sum2]=x;
    }
    for(int i=1;i<=17;i++)
    {
        f2[x][i]=f2[f2[x][i-1]][i-1];
    }
    if(ls2[x])
    {
        dfs2(ls2[x]);
    }
    if(rs2[x])
    {
        dfs2(rs2[x]);
    }
    t2[x]=sum2;
}
int ST1(int x,int val)
{
    for(int i=17;i>=0;i--)
    {
        if(v1[f1[x][i]]>=val&&f1[x][i]!=0)
        {
            x=f1[x][i];
        }
    }
    return x;
}
int ST2(int x,int val)
{
    for(int i=17;i>=0;i--)
    {
        if(v2[f2[x][i]]<=val&&f2[x][i]!=0)
        {
            x=f2[x][i];
        }
    }
    return x;
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&a[i].u,&a[i].v);
        a[i].u++;
        a[i].v++;
        a[i].x=min(a[i].u,a[i].v);
        a[i].y=max(a[i].u,a[i].v);
    }
    for(int i=1;i<2*n;i++)
    {
        fa1[i]=i;
        fa2[i]=i;
    }
    num1=num2=n;
    sort(a+1,a+1+m,cmp1);
    for(int i=1;i<=m;i++)
    {
        int fx=find1(a[i].u);
        int fy=find1(a[i].v);
        if(fx!=fy)
        {
            num1++;
            v1[num1]=a[i].x;
            ls1[num1]=fx;
            rs1[num1]=fy;
            f1[fx][0]=num1;
            f1[fy][0]=num1;
            fa1[fx]=num1;
            fa1[fy]=num1;
            if(num1==2*n-1)
            {
                break;
            }
        }
    }
    for(int i=1;i<=n;i++)
    {
        if(!vis1[i])
        {
            dfs1(find1(i));
        }
    }
    sort(a+1,a+1+m,cmp2);
    for(int i=1;i<=m;i++)
    {
        int fx=find2(a[i].u);
        int fy=find2(a[i].v);
        if(fx!=fy)
        {
            num2++;
            v2[num2]=a[i].y;
            ls2[num2]=fx;
            rs2[num2]=fy;
            f2[fx][0]=num2;
            f2[fy][0]=num2;
            fa2[fx]=num2;
            fa2[fy]=num2;
            if(num2==2*n-1)
            {
                break;
            }
        }
    }
    for(int i=1;i<=n;i++)
    {
        if(!vis2[i])
        {
            dfs2(find2(i));
        }
    }
    build(root[0],1,n);
    for(int i=1;i<=n;i++)
    {
        updata(root[i],root[i-1],1,n,s2[q1[i]]+1);
    }
    while(k--)
    {
        scanf("%d%d%d%d",&s,&t,&l,&r);
        s++;
        t++;
        l++;
        r++;
        s=ST1(s,l);
        t=ST2(t,r);
        int ans=query(root[s1[s]],root[t1[s]],1,n,s2[t]+1,t2[t]);
        if(ans==0)
        {
            printf("0\n");
        }
        else
        {
            printf("1\n");
        }
    }
}

原文地址:https://www.cnblogs.com/Khada-Jhin/p/9751803.html

时间: 2024-10-07 16:06:57

[IOI2018]werewolf狼人——kruskal重构树+可持久化线段树的相关文章

[IOI2018] werewolf 狼人 kruskal重构树,主席树

[IOI2018] werewolf 狼人 LG传送门 kruskal重构树好题. 日常安利博客文章 这题需要搞两棵重构树出来,这两棵重构树和我们平时见过的重构树有点不同(据说叫做点权重构树?),根据经过我们简化的建树方法,这两棵树不再是二叉树,但是仍具有kruskal重构树的优秀性质,建议结合后面的描述理解. 看这题需要首先我们从\(S\)走到\(T\)转化为分别从\(S\)和\(T\)出发寻找能共同到达的点,需要快速求出从某个点出发经过点权不大(小)于\(r\)(\(l\))的点,考虑kru

BZOJ 3551 ONTAK2010 Peaks加强版 Kruskal重构树+可持久化线段树

题目大意:同3545 强制在线 3545题解传送门:http://blog.csdn.net/popoqqq/article/details/40660953 强制在线没法排序 启发式合并也就用不了了 Kruskal重构树是个挺好玩的东西 可以拿来处理一些最小生成树的边权最值问题 这里我们Kruskal连边时并不直接连边 而是新建一个节点ext 将两个点所在子树都连到ext的儿子上 比如说样例的树就建成了这样 图中红色的是原图的边权,黑色的是原图上的点 这样生成的树有一些十分优美的性质: 1.二

【BZOJ 3551】[ONTAK2010] Peaks加强版 Kruskal重构树+树上倍增+主席树

这题真刺激...... I.关于Kruskal重构树,我只能开门了,不过补充一下那玩意还是一棵满二叉树.(看一下内容之前请先进门坐一坐) II.原来只是用树上倍增求Lca,但其实树上倍增是一种方法,Lca只是他的一种应用,他可以搞各种树上问题,树上倍增一般都会用到f数组. |||.我们跑出来dfs序就能在他的上面进行主席树了. IV.别忘了离散. V.他可能不连通,我一开始想到了,但是我觉得出题人可能会是好(S)人(B),但是...... #include <cstdio> #include

[IOI2018] werewolf 狼人

[IOI2018] werewolf 狼人 IOI2018题解 代码: #include<bits/stdc++.h> #define reg register int #define il inline #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;x=0;bool fl=false; while(!isdigit(ch=getchar()))(

Kruskal重构树

不支持时间旅行的可持久化并查集 给定 n 个点, 以及 m 次操作, 操作有两种: ① 将点 x 与点 y 进行连边; ② 询问在前 t 次操作操作中, x 与 y 是否连通. n <= 100000, 强制在线. 核心模型 n 个点, m 条带权边的无向图. 多次询问点 x 和点 y 在边权不超过 w 的边的作用下的连通性信息(例如, 是否连通). 强制在线. 对于不支持时间旅行的并查集问题, 将时间这一维给量化之后, 可以等价地看成这个核心模型. 对于操作①, 我们相当于连边 (x, y)

【BZOJ 3732】 Network Kruskal重构树+倍增LCA

Kruskal重构树裸题, Sunshine互测的A题就是Kruskal重构树,我通过互测了解到了这个神奇的东西... 理解起来应该没什么难度吧,但是我的Peaks连WA,,, 省选估计要滚粗了TwT #include<cstdio> #include<cstring> #include<algorithm> #define for1(i,a,n) for(int i=(a);i<=(n);i++) #define for2(i,a,n) for(int i=(a

BZOJ 3732 Network Kruskal重构树

题目大意:给定一个n个点m条边的无向连通图,k次询问两点之间所有路径中最长边的最小值 Kruskal+倍增LCA做法见http://blog.csdn.net/popoqqq/article/details/39755703 LCT做法见http://blog.csdn.net/popoqqq/article/details/39929277 Kruskal重构树真是强大--一不小心手滑就RANK1啥的-- 每加入一条边时,我们并不链接这条边的两端点,而是把这条边两端点所在并查集的根连接起来,而

算法学习——kruskal重构树

kruskal重构树是一个比较冷门的数据结构. 其实可以看做一种最小生成树的表现形式. 在普通的kruskal中,如果一条边连接了在2个不同集合中的点的话,我们将合并这2个点所在集合. 而在kruskal重构树中,如果一条边连接了在2个不同集合中的点,我们将新建一个节点出来,并用这个新节点作为一个中转连接这2个集合. 如图就是一棵kruskal重构树,方点表示新建出的节点,圆点是原图中的点,方点点权即边权. 这样建出的树会有一些美妙的性质,例如往上走点权是递增的,原图中的每个点都是叶子节点等.

Gym - 101173H Hangar Hurdles (kruskal重构树/最小生成树+LCA)

题目大意:给出一个n*n的矩阵,有一些点是障碍,给出Q组询问,每组询问求两点间能通过的最大正方形宽度. 首先需要求出以每个点(i,j)为中心的最大正方形宽度mxl[i][j],可以用二维前缀和+二分或者BFS求. 然后每相邻的两个点建一条权值为min(mxl[i][j],mxl[i'][j'])的边,求出整个图的最小生成树(注意边权要从大到小排序,实际上求出的是边权的“最大生成树”)或者kruskal重构树,对于每组询问(x1,y1),(x2,y2),答案为最小生成树上两点间路径的最小边权,或者