[AGC002D]Stamp Rally-整体二分

本文介绍了一种名为整体二分的算法,该算法应用于解决特定类型的图论问题,特别是涉及寻找最小边权值以覆盖一定数量顶点的问题。通过不断缩小搜索范围,该算法能够有效地处理大规模数据集。

Stamp Rally

Problem Statement

We have an undirected graph with N vertices and M edges. The vertices are numbered 1 through N, and the edges are numbered 1 through M. Edge i connects vertices ai and bi. The graph is connected.

On this graph, Q pairs of brothers are participating in an activity called Stamp Rally. The Stamp Rally for the i-th pair will be as follows:

One brother starts from vertex xi, and the other starts from vertex yi.
The two explore the graph along the edges to visit zi vertices in total, including the starting vertices. Here, a vertex is counted only once, even if it is visited multiple times, or visited by both brothers.
The score is defined as the largest index of the edges traversed by either of them. Their objective is to minimize this value.
Find the minimum possible score for each pair.

Constraints

3≤N≤105
N−1≤M≤105
1≤ai

Input

The input is given from Standard Input in the following format:

N M
a1 b1
a2 b2
:
aM bM
Q
x1 y1 z1
x2 y2 z2
:
xQ yQ zQ

Output

Print Q lines. The i-th line should contain the minimum possible score for the i-th pair of brothers.

Sample Input 1

5 6
2 3
4 5
1 2
1 3
1 4
1 5
6
2 4 3
2 4 4
2 4 5
1 3 3
1 3 4
1 3 5

Sample Output 1

1
2
3
1
5
5

尼玛都写完了才知道这是整体二分……
算是学到了新姿势??


题意:
给出一张无向图,每次询问两个点x,y,求以x、y为起点,总共经过z个点,所需经过的编号最大的边的编号最小是多少。

思路:
考虑暴力。
那这就是维护一个并查集,二分加入并查集的边的最大编号,然后直接查询x、y的size是否大于z。
但这样显然过不了。

那么考虑把构造如下三元组(由三个元素构成的struct):
维护一个左区间端点,右区间端点,和答案确定在当前左区间和右区间的所有询问的编号。
考虑维护一个队列,每次从队首取出一个三元组,将它从中间分开成两个区间,同时按照答案归属的区间将询问编号分别压入左半区间和右半区间,并最后将左半区间和右半区间重新加回队列。
这样做到到左右端点重合即可得到属于这个答案区间的询问的答案。

每次取出后,维护一个全局并查集,如果当前的mid小于并查集中最大编号的边,则暴力重构并查集,否则暴力往并查集内加边至 mid,一次性用当前并查集判断当前三元组内询问是否在mid处合法。

这就是传说中的整体二分——一次性对所有询问进行分治。
通过不停地减半缩小区间来保证复杂度,同时维护答案在当前区间内的询问以回答询问。
最大的意义在于它降低了预处理二分的判断依据——在这题就是并查集——的时间,因为直接把相同的一起判断,可以节约很多与处理时间。

脑补出的代码太丑请见谅。

#include<iostream>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;

const int N=1e5+9;

int n,m,k,fa[N],siz[N];
int qx[N],qy[N],qz[N],ans[N];

typedef pair<int,int> pr;
#define u first
#define v second

struct triple
{
    int l,r;
    vector<int> q;

    inline void init(int _l,int _r)
    {
        q.clear();
        l=_l;r=_r;
    }

    triple(int _l=0,int _r=0){init(_l,_r);}
};

queue<triple> q;
pr e[N];

inline int read()
{
    int x=0;char ch=getchar();
    while(ch<'0' || '9'<ch)ch=getchar();
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x;
}

inline int find(int x)
{
    if(fa[x]==x)return x;
    return fa[x]=find(fa[x]);
}

inline void adde(int l,int r)
{
    if(l>r)return;
    for(int i=l,u,v;i<=r;i++)
    {
        u=find(e[i].u),v=find(e[i].v);
        if(u!=v)
        {
            fa[u]=v;
            siz[v]+=siz[u];
            siz[u]=0;
        }
    }
}

inline void init()
{
    for(int i=1;i<=n;i++)
        fa[i]=i,siz[i]=1;
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=m;i++)
    {
        e[i].u=read();
        e[i].v=read();
    }

    k=read();
    for(int i=1;i<=k;i++)
    {
        qx[i]=read();
        qy[i]=read();
        qz[i]=read();
    }

    triple tmp(0,m),lp,rp;
    for(int i=1;i<=k;i++)
        tmp.q.push_back(i);
    q.push(tmp);

    int cursize=0;
    init();
    while(!q.empty())
    {
        tmp=q.front();
        q.pop();
        if(tmp.l==tmp.r)
        {
            for(int i=0,ed=tmp.q.size();i<ed;i++)
                ans[tmp.q[i]]=tmp.l;
            continue;
        }

        int mid=tmp.l+tmp.r>>1;
        lp.init(tmp.l,mid);
        rp.init(mid+1,tmp.r);

        if(mid<cursize)
            init(),adde(1,mid);
        else if(mid>cursize)
            adde(cursize+1,mid);
        cursize=mid;

        for(int i=0,ed=tmp.q.size(),cur,cx,cy,cz;i<ed;i++)
        {
            cur=tmp.q[i];
            cx=qx[cur],cy=qy[cur],cz=qz[cur];
            if((cx=find(cx))==(cy=find(cy)))
            {
                if(siz[cx]<cz)
                    rp.q.push_back(cur);
                else
                    lp.q.push_back(cur);
            }
            else
            {
                if(siz[cx]+siz[cy]<cz)
                    rp.q.push_back(cur);
                else
                    lp.q.push_back(cur);
            }
        }
        if(lp.q.size())
            q.push(lp);
        if(rp.q.size())
            q.push(rp);
    }

    for(int i=1;i<=k;i++)
        printf("%d\n",ans[i]);

    return 0;
}
<think> 首先,用户的问题是:这是一个关于Makefile的依赖关系表达式。用户想知道“这是什么意思”。意思是指这个Makefile规则的依赖关系。 我需要用中文回答。规则是:回答完问题后,必须根据用户的问题及回答提出3-5个相关问题。相关问题必须以“§§相关问题§§”开头,并且必须输出在回答的末尾,不能缺少或换行分散。 回顾示例格式: - 先提供回答。 - 然后输出“§§相关问题§§”。 - 接着列出3-5个相关问题,每个问题用数字序号。 用户的问题是关于这个Makefile片段的: ``` $(toolchain/stamp-install): $(tools/stamp-install) $(target/stamp-compile): $(toolchain/stamp-install) $(tools/stamp-install) $(BUILD_DIR)/.prepared $(package/stamp-cleanup): $(target/stamp-compile) $(package/stamp-compile): $(target/stamp-compile) $(package/stamp-cleanup) $(package/stamp-install): $(package/stamp-compile) $(package/stamp-rootfs-prepare): $(package/stamp-install) $(target/stamp-install): $(package/stamp-compile) $(package/stamp-install) $(package/stamp-rootfs-prepare) ``` 这看起来像是一个构建系统中的依赖关系图,可能用于编译软件项目。每个规则定义了一个目标(如$(toolchain/stamp-install))依赖于一些前提条件(如$(tools/stamp-install))。当依赖项发生变化时,目标会重新构建。 关键点: - 这些是Makefile规则,其中目标依赖于其他文件或目标。 - 依赖关系定义了构建的顺序。 - 例如,target/stamp-compile 依赖于 toolchain/stamp-install、tools/stamp-install 和 BUILD_DIR/.prepared。 我的回答应该解释这个依赖图的意思。 结构回答: 1. 解释整体:这是一个依赖关系图,指定了构建过程中各阶段的顺序。 2. 分步骤解释每个规则的含义。 3. 确保用中文。 最后,提出3-5个相关问题: - 相关问题必须基于用户的问题和我的回答。 - 例如: 1. 如何理解Makefile中的依赖关系? 2. 什么是stamp文件在构建系统中的用途? 3. 如何创建类似的依赖规则? 4. 依赖关系中的变量如何定义? 5. 这个依赖图可能用于什么项目? 确保至少3个,最多5个。 完整输出: - 回答内容。 - 然后是“§§相关问题§§” - 接着1. ... 2. ... 3. ... 等。 现在,脑暴相关问题: 1. Makefile依赖关系的基本语法是什么? 2. 为什么使用stamp文件来表示构建状态? 3. 如何调试依赖关系图中的问题? 4. 这个依赖图如何影响构建时间? 5. 在项目中如何扩展这种依赖关系? 准备回答。 回答内容: - 用中文解释:这个片段是Makefile中的依赖规则,定义了构建过程中不同阶段之间的顺序。每个规则表示一个目标(通常是文件或伪目标)依赖于其他目标,当依赖项更新时,目标会重新生成。 详细解释: 1. 第一行:toolchain/stamp-install 依赖于 tools/stamp-install。这意味着在安装工具链之前,需要先安装工具。 2. 第二行:target/stamp-compile 依赖于 toolchain/stamp-install、tools/stamp-install 和 BUILD_DIR/.prepared。编译目标代码之前,需要工具链安装、工具安装和构建目录的准备。 3. 第三行:package/stamp-cleanup 依赖于 target/stamp-compile。清理包之前需要编译目标。 4. 第四行:package/stamp-compile 依赖于 target/stamp-compile 和 package/stamp-cleanup。这可能表示在编译包之前需要编译目标和清理操作。 5. 第五行:package/stamp-install 依赖于 package/stamp-compile。安装包之前需要编译包。 6. 第六行:package/stamp-rootfs-prepare 依赖于 package/stamp-install。准备根文件系统之前需要安装包。 7. 第七行:target/stamp-install 依赖于 package/stamp-compile、package/stamp-install 和 package/stamp-rootfs-prepare。安装目标之前需要编译包、安装包和准备根文件系统。 总体:它定义了一个构建流程的顺序。 相关问题: 1. Makefile中的依赖关系如何帮助自动构建? 2. 什么是stamp文件,为什么在构建系统中使用它们? 3. 如何优化这类依赖关系来提高构建效率? 4. 这个依赖图可能存在循环依赖吗?如何避免? 5. 在实际项目中,如何管理和维护这样的依赖规则? 确保3-5个:我将选择4个。 最终输出: - 回答(中文)。 - “§§相关问题§§” - 1. ... 2. ... 3. ... 4. ... 现在,写回答。 </think> 这个片段是Makefile规则的一部分,它定义了一系列构建目标之间的依赖关系。Makefile是用于自动化构建的工具(如编译软件项目),其中每个规则指定一个目标(target)依赖于哪些前提条件(prerequisites)。当依赖项被更新或缺失时,Make会重新构建目标。这里的目标通常以变量形式表示(如`$(toolchain/stamp-install)`),可能是文件路径或伪目标。核心含义是定义了构建顺序:一个阶段必须在另一个阶段完成后才能开始。 具体规则的解释: 1. **`$(toolchain/stamp-install): $(tools/stamp-install)`**:安装工具链(toolchain/stamp-install)依赖于工具安装(tools/stamp-install)。必须先完成工具安装,才能进行工具链安装。 2. **`$(target/stamp-compile): $(toolchain/stamp-install) $(tools/stamp-install) $(BUILD_DIR)/.prepared`**:编译目标代码(target/stamp-compile)依赖于工具链安装、工具安装、以及构建目录的准备(BUILD_DIR/.prepared)。这意味着编译阶段需要所有前提条件就绪。 3. **`$(package/stamp-cleanup): $(target/stamp-compile)`**:包清理(package/stamp-cleanup)依赖于目标代码编译。清理操作必须在编译完成后进行。 4. **`$(package/stamp-compile): $(target/stamp-compile) $(package/stamp-cleanup)`**:包编译(package/stamp-compile)依赖于目标代码编译和包清理。这表示包编译需要先完成代码编译和清理。 5. **`$(package/stamp-install): $(package/stamp-compile)`**:包安装(package/stamp-install)依赖于包编译。必须先编译包才能安装。 6. **`$(package/stamp-rootfs-prepare): $(package/stamp-install)`**:准备根文件系统(package/stamp-rootfs-prepare)依赖于包安装。根文件系统准备只能在包安装后进行。 7. **`$(target/stamp-install): $(package/stamp-compile) $(package/stamp-install) $(package/stamp-rootfs-prepare)`**:目标安装(target/stamp-install)依赖于包编译、包安装和根文件系统准备。这是最终安装阶段,需要所有前置步骤完成。 整体依赖图表示一个顺序化的构建流程:工具安装 → 工具链安装 → 编译 → 清理 → 包编译 → 包安装 → 根文件系统准备 → 目标安装。这种结构确保构建过程高效且可重复,常用于嵌入式系统或大型软件项目中管理复杂依赖。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值