CodeForces 466E. Information Graph (并查集+倍增)

本文解析了CodeForces上的一道题目,介绍了如何使用并查集和倍增算法处理员工间的上下级关系及文件签署查询。通过路径压缩优化查询效率,实现在线变离线的算法策略。

题目: http://codeforces.com/problemset/problem/466/E

题意:
三种操作:
1.y变为x的boss;
2.x签署一份文件并逐级往上传并签署;
3.查询第x个人是否签署第i份文件。

分析:
在线变离线;
用路径压缩的并查集能直接查询当前状态下某个人的最高boss;
签署一份文件时,记录签署此文件最底端的人和最顶端的人;
倍增处理出每个节点往上走1<<i步是谁;
处理出每个节点的深度deep[i]。

对于每个查询:
从第i个文件的最底端往上走(deep[x]-deep[i文件底端])步;
并查询走到的这个节点是否为x。

代码:

//jerry99
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int tmax=1e5+5;
int n,m,fa[tmax][20],f[tmax],deep[tmax];
int Start[tmax],End[tmax],num,numq;
struct node{
    int people,packnum;
};
node q[tmax];
vector<int> G[tmax];
int find(int x)
{
    return f[x]==x?x:f[x]=find(f[x]);
}
void dfs(int x,int d)
{
    deep[x]=d;
    int i,len=G[x].size();
    for(i=0;i<len;i++)
        dfs(G[x][i],d+1);
    return;
}
void init()
{
    int i,j;
    for(j=1;j<=17;j++)
        for(i=1;i<=n;i++)
            fa[i][j]=fa[fa[i][j-1]][j-1];
    for(i=1;i<=n;i++)
        if(find(i)==i)
            dfs(i,1);
    return;
}
void work()
{
    int i,j,Dif,s,t,check;
    for(j=1;j<=numq;j++)
    {
        s=Start[q[j].packnum];
        t=End[q[j].packnum];
        check=q[j].people;
        //printf("s=%d t=%d\n",s,t);
        if(find(s)!=find(check)||deep[s]<deep[check]||deep[check]<deep[t])
        {
            printf("NO\n");
            continue;
        }
        Dif=deep[s]-deep[check];
        for(i=17;i>=0;i--)
        {
            if(Dif<(1<<i)) continue;
            Dif-=(1<<i);
            s=fa[s][i];
            if(s==0) break;
        }
        if(s!=check) printf("NO\n");
        else printf("YES\n");
    }
    return;
}
int main()
{
    scanf("%d%d",&n,&m);
    int i,t,x,y;
    for(i=1;i<=n;i++)
        f[i]=i;
    for(i=1;i<=m;i++)
    {
        scanf("%d",&t);
        if(t==1)
        {
            scanf("%d%d",&x,&y);
            fa[x][0]=y;
            G[y].push_back(x);
            f[x]=find(y);
        }
        else if(t==2)
        {
            num++;
            scanf("%d",&Start[num]);
            End[num]=find(Start[num]);
        }
        else
        {
            numq++;
            scanf("%d%d",&q[numq].people,&q[numq].packnum);
        }
    }
    init();
    work();
    return 0;
}
### 关于 CodeForces 892E 的解题思路分析 #### 使用可撤销并查集解决最小生成树中的边集合验证问题 针对给定的无向图以及多个询问,每个询问涉及一组特定的边,并要求判断这组边能否同时存在于某棵最小生成树中。此问题可以通过结合Kruskal算法构建最小生成树的过程来求解,在这一过程中利用到的是按照权重升序排列后的边逐步加入至森林结构之中[^1]。 为了高效处理多次查询而不影响后续操作的结果,引入了带有回溯功能的数据结构——即所谓的“可撤销并查集”。这种特殊形式的并查集允许执行合并(union)的同时记录下每一次变动以便之后能够恢复原状;当完成一次查询判定后即可通过一系列反向动作使数据结构回到初始状态,从而不影响其他独立事件的发生逻辑[^3]。 具体实现方法如下: - 将所有的边依据其权重从小到大排序; - 对每一个询问所涉及到的边也做同样的预处理; - 开始遍历已排序好的全局边列表,每当遇到属于当前待检验询问范围内的边时,则尝试将其纳入现有连通分量内; - 如果发现形成环路则说明该询问无法满足条件; - 同样地,任何不属于当前询问但同样处于相同权值下的其它边也应该被考虑进来以确保最终形成的MST是最优解的一部分; - 完成一轮测试后记得清除所有临时更改使得系统重置为未受干扰的状态准备迎接下一个挑战。 ```cpp #include <bits/stdc++.h> using namespace std; struct Edge { int u, v; }; class DSUWithRollback { public: vector<int> parent, rank, historyParent, historyRank; void init(int n){ parent.resize(n); iota(parent.begin(), parent.end(), 0); // Fill with identity mapping. rank.assign(n, 0); historyParent.clear(); historyRank.clear(); } int findSet(int i) {return (parent[i]==i)?i:(findSet(parent[i]));} bool isSameSet(int i, int j){ return findSet(i)==findSet(j);} void unionSets(int i, int j){ if (!isSameSet(i,j)){ historyParent.push_back(findSet(i)); historyParent.push_back(findSet(j)); historyRank.push_back(rank[findSet(i)]); historyRank.push_back(rank[findSet(j)]); int x=findSet(i), y=findSet(j); if (rank[x]>rank[y]) swap(x,y); parent[x]=y; if (rank[x]==rank[y]) ++rank[y]; } } void rollback(){ while(!historyParent.empty()){ parent[historyParent.back()]=historyParent.back(); historyParent.pop_back(); rank[historyParent.back()] = historyRank.back(); historyParent.pop_back(); historyRank.pop_back(); } } }; ``` 上述代码展示了如何创建一个支持撤销机制的并查集类`DSUWithRollback`,它可以在不破坏原有连接关系的前提下安全地进行节点间的联合与查找操作。此外还提供了用于追踪变化历史的方法,方便在必要时候撤消最近的一系列更动。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值