【AtCoder1998】Stamp Rally(整体二分+并查集)

题意

我们有一个N (3<=N<=10^5)个结点和M(N−1≤M≤10^5)个边的无向图。 结点编号为1到N,边编号为1到M。边i连接结点ai和bi。保证图连通。在这张图上,Q(1≤Q≤10^5)对兄弟正在参加一项名为Stamp Rally的活动。 第i对Stamp Rally如下:
一个兄弟从结点xi开始,另一个从结点yi开始。(1≤xi < yi≤N)
两个兄弟沿着边访问图上的结点,总共访问zi(3≤zi≤N)个结点,包括起始结点。 在这里,即使一个结点被多次访问,只计算一次。
定义得分为它们走过的边的最大编号。 他们的目标是尽量减少这个得分。
找出每对兄弟的最低分数。

题解

以边的编号从小到大加入图中,利用并查集,每加入一条边i,扫描所有询问,如果他们所在联通快大小达到了zi,则这个询问的答案 为i。
但这样是 O(MQ) O ( M Q ) 的。
利用整体二分,先加入前面一半 [l,mid] [ l , m i d ] 的边,判断哪些询问已经达到条件,则这些询问的答案一定 mid ≤ m i d ,将其划分到前面一半,然后将并查集复原,递归处理 [l,mid] [ l , m i d ] 的询问,和 [mid+1,r] [ m i d + 1 , r ] 的询问。
为了使并查集复原,可使用按秩合并。

代码

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int MAXN=100005;

int N,M,Q;

struct Edge
{
    int u,v;
}E[MAXN];
struct Query
{
    int x,y,z;
}q[MAXN];
int id[MAXN],tmp[MAXN],mk[MAXN],ans[MAXN];

int stk[MAXN][2],tp;
int fa[MAXN],siz[MAXN];
int Root(int u)
{
    if(fa[u]==0)
        return u;
    return Root(fa[u]);
}
void Union(int u,int v)
{
    int r1=Root(u),r2=Root(v);
    if(r1==r2)
        return;
    if(siz[r1]<siz[r2])
        swap(r1,r2);
    fa[r2]=r1;
    stk[++tp][0]=r2;
    stk[tp][1]=r1;
    siz[r1]+=siz[r2];
}

void solve(int a,int b,int l,int r)
{
    if(a==b)
    {
        for(int i=l;i<=r;i++)
            ans[id[i]]=a;
        Union(E[a].u,E[a].v);
        return;
    }
    tp=0;
    int mid1=(a+b)/2;
    for(int i=a;i<=mid1;i++)
        Union(E[i].u,E[i].v);
    for(int i=l;i<=r;i++)
    {
        int r1=Root(q[id[i]].x),r2=Root(q[id[i]].y);
        if(r1==r2&&siz[r1]>=q[id[i]].z)
            mk[i]=1;
        if(r1!=r2&&siz[r1]+siz[r2]>=q[id[i]].z)
            mk[i]=1;
    }
    int it=l-1;
    for(int i=l;i<=r;i++)
        if(mk[i])
            tmp[++it]=id[i];
    int mid2=it;
    for(int i=l;i<=r;i++)
        if(!mk[i])
            tmp[++it]=id[i];
    for(int i=l;i<=r;i++)
        id[i]=tmp[i],mk[i]=0;
    while(tp)
    {
        fa[stk[tp][0]]=0;
        siz[stk[tp][1]]-=siz[stk[tp][0]];
        tp--;
    }
    solve(a,mid1,l,mid2);
    solve(mid1+1,b,mid2+1,r);
}

int main()
{
    scanf("%d%d",&N,&M);
    for(int i=1;i<=M;i++)
        scanf("%d%d",&E[i].u,&E[i].v);
    scanf("%d",&Q);
    for(int i=1;i<=Q;i++)
        scanf("%d%d%d",&q[i].x,&q[i].y,&q[i].z);
    for(int i=1;i<=N;i++)
        fa[i]=0,siz[i]=1;
    for(int i=1;i<=Q;i++)
        id[i]=i;
    solve(1,M,1,Q);
    for(int i=1;i<=Q;i++)
        printf("%d\n",ans[i]);

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值