Meteors 题解
蒟蒻初学整体二分,写一篇题解记录一下思考与看法。
题目大意
在一个环形的轨道上分别着若干国家的空间站,在接下来的一段时间内会出现若干次陨石,每次出现在环形的某一段轨道,每个国家都想收集一定量的陨石,需要判断每个国家最少在什么时候可以集齐所需的陨石。
思路分析
首先可以想到一个简单而暴力的做法:
枚举每一个国家,逐一将每一次陨石落下,判断其在哪一个时间收集满了所需的陨石。
但这样的时间复杂度是 \(O(n^2)\)(令 \(n,m,k\) 同阶)的,没有办法解决这个问题。
需要更优的解法!
容易发现,对于每一个国家,答案都是单调不降的,这启示我们可以使用二分!
那么我们首先会想到,对于每一个国家,二分其收集满陨石的时刻。
但这样的时间复杂度是 \(O(n^2\log n)\) 的,甚至还没有暴力优秀。
因此,我们需要转化思考角度。
我们发现,在对于每个国家进行二分时,我们都降落了很多次陨石(平均 \(n\log n\) 次),这肯定是巨亏的,那么,我们是否可以不枚举国家进行降落陨石,而是改为降落陨石,枚举每个国家的时间呢?
这时,整体二分就闪亮登场了!
整体二分的思想是将很多个需要二分的对象放在一起,通过改变枚举对象来达到优化时间复杂度的目标。
比如在这题中,我们可以将所有的国家放在一起进行二分,在二分时只降落区间中点前半部分的陨石,在查询每个国家是否收集满,并以此将其划分到子区间中。
具体的说,我们设计函数 solve(l,r,x,y) 表示 \([l,r]\) 的陨石对 \([x,y]\) 的国家的贡献。(比较抽象?感性理解)
首先考虑边界,当 \(l=r\) 时,我们可以确定答案。
否则,我们另 \(\text{mid}=\frac{l+r}{2}\),将从 \(l\) 到 \(\text{mid}\) 之间的陨石全部降落,并将这个区间的所有的国家进行判断,如果已经收集满了,我们就将它划分到左区间中,否则划分到右区间中。
这样递归下去我们就可以得到所有国家的答案了!
时间复杂度?我们需要递归 \(\log n\) 层,每层需要把一半的陨石降落,陨石降落是一个区间操作,我们可以通过一个数据结构将降落过程优化到 \(\log n\) 级别,因此整体二分的时间复杂度是 \(O(n\log^2n)\) 的。(是不是比之前的做法优秀许多?)
整体二分的思想比较有意思,它通过转化统计和二分的对象来解决问题。
还有很多类似的思想:更换定义域和值域的权值线段树,通过离线来随意操纵时间线的 \(\text{cdq}\) 分治,通过倍增对值域进行划分的倍增分块等等,它们无疑是人类智慧的结晶,化不可做为可做,化不可能为可能。
代码
注释超详细
#include <bits/stdc++.h>
using namespace std;
const int N=600100;//因为是环,要断环成链,所以开双倍
typedef long long ll;
int n,m,k,in1,idx;
int to[N],nxt[N],ans[N];//我们需要用邻接表来存储一个国家的某个空间站的下一个空间站,不能每次遍历轨道,不然会 T(时间复杂度不变,但常数会变大)
struct country{int head,id;ll need;}con[N],con2[N];//国家的结构体,另一个是用来暂时存储的,head 是该国家邻接表的表头,id是国家编号,need是所需的陨石数量
struct stone{int l,r;ll a;}sto[N];//陨石结构体,左右端点和陨石数量
void add(int u,int v){idx++;to[idx]=v;nxt[idx]=con[u].head;con[u].head=idx;}//邻接表加边
struct Tree_array{//选择树状数组来辅助陨石下落
    ll a[N];
    #define lowbit(x) ((x)&(-(x)))
    void change(int x,ll k){for(;x<=(m<<1);x+=lowbit(x)) a[x]+=k;}
    ll ask(int x){ll ans=0;for(;x;x-=lowbit(x)) ans+=a[x];return ans;}//树状数组常规操作,单点加,区间查询
}tree;
void solve(int l,int r,int x,int y){
    if(l==r){//到达边界了!
        for(int i=x;i<=y;i++)
            ans[con[i].id]=l;//得到该区间的国家的答案
        return ;//速润
    }
    int mid=(l+r)>>1,ll=0,rr=n;//mid是区间中点,ll,rr是辅助暂时存储国家的两个计数变量
    for(int i=l;i<=mid;i++){
        tree.change(sto[i].l,sto[i].a);
        tree.change(sto[i].r+1,-sto[i].a);
    }//通过差分操作让树状数组实现区间修改
    for(int s=x;s<=y;s++){
        long long sum=0;
        for(int i=con[s].head;i&&sum<=con[s].need;i=nxt[i])
            sum+=tree.ask(to[i]+m)+tree.ask(to[i]);//对于这个国家,统计信息(树状数组的区间查询变成了单点查询,又因为断环成链所以要查询两个部分)
        if(sum>=con[s].need) con2[++ll]=con[s];//划分到左区间
        else con2[++rr]=con[s],con2[rr].need-=sum;//划分到右区间,同时需要的陨石减去这一部分(这样才可以保证以后的递归过程中只需要降落子区间前半部分的陨石就可以统计答案)
    }
    for(int i=l;i<=mid;i++){
        tree.change(sto[i].l,-sto[i].a);
        tree.change(sto[i].r+1,sto[i].a);
    }//把陨石送回去
    for(int i=1;i<=ll;i++)
        con[x+i-1]=con2[i];//copy一份
    for(int i=n+1;i<=rr;i++)
        con[x+ll+i-n-1]=con2[i];
    solve(l,mid,x,x+ll-1);//递归左右子区间
    solve(mid+1,r,y-rr+n+1,y);
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d",&in1);
        add(in1,i);//加边
    }
    for(int i=1;i<=n;i++){
        scanf("%lld",&con[i].need);//读入国家信息
        con[i].id=i;
    }
    scanf("%d",&k);
    for(int i=1;i<=k;i++){
        scanf("%d%d%d",&sto[i].l,&sto[i].r,&sto[i].a);
        if(sto[i].r<sto[i].l) sto[i].r+=m;//更新左端点
    }
    solve(1,k+1,1,n);//右端点划分到k+1,方便判断无解
    for(int i=1;i<=n;i++){
        if(ans[i]==k+1) cout<<"NIE\n";
        else cout<<ans[i]<<'\n';
    }
    return 0;
}
Meteors 题解的更多相关文章
- BZOJ2527 & 洛谷3527:[Poi2011]Meteors——题解
		+++++++++++++++++++++++++++++++++++++++++++ +本文作者:luyouqi233. + +欢迎访问我的博客:http://www.cnblogs.com/luy ... 
- [Poi2011]Meteors 题解
		题目大意: 给定一个环,每个节点有一个所属国家,k次事件,每次对[l,r]区间上的每个点点权加上一个值,求每个国家最早多少次操作之后所有点的点权和能达到一个值. 思路: 整体二分(二分答案),对于每个 ... 
- 一篇自己都看不懂的CDQ分治&整体二分学习笔记
		作为一个永不咕咕咕的博主,我来更笔记辣qaq CDQ分治 CDQ分治的思想还是比较简单的.它的基本流程是: \(1.\)将所有修改操作和查询操作按照时间顺序并在一起,形成一段序列.显然,会影响查询操作 ... 
- BZOJ2527: [Poi2011]Meteors
		补一发题解.. 整体二分这个东西,一开始感觉复杂度不是很靠谱的样子 问了po姐姐,说套主定理硬干.. #include<bits/stdc++.h> #define ll long lon ... 
- BZOJ 2527 Meteors | 整体二分
		BZOJ 2527 Meteors 题意 一个圆环上有m个位置,编号为1~m,分别属于n个国家. 有k个时刻,每个时刻都会给圆环上的一个区间中每个位置的值加上一个数. 每个国家有一个目标,问对于每个国 ... 
- POI2011题解
		POI2011题解 2214先咕一会... [BZOJ2212][POI2011]Tree Rotations 线段树合并模板题. #include<cstdio> #include< ... 
- 【BZOJ2527】[Poi2011]Meteors 整体二分
		[BZOJ2527][Poi2011]Meteors Description Byteotian Interstellar Union (BIU) has recently discovered a ... 
- 2016 华南师大ACM校赛 SCNUCPC 非官方题解
		我要举报本次校赛出题人的消极出题!!! 官方题解请戳:http://3.scnuacm2015.sinaapp.com/?p=89(其实就是一堆代码没有题解) A. 树链剖分数据结构板题 题目大意:我 ... 
- noip2016十连测题解
		以下代码为了阅读方便,省去以下头文件: #include <iostream> #include <stdio.h> #include <math.h> #incl ... 
- BZOJ-2561-最小生成树 题解(最小割)
		2561: 最小生成树(题解) Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1628 Solved: 786 传送门:http://www.lyd ... 
随机推荐
- AI 和 DevOps:实现高效软件交付的完美组合
			AI 时代,DevOps 与 AI 共价结合.AI 由业务需求驱动,提高软件质量,而 DevOps 则从整体提升系统功能.DevOps 团队可以使用 AI 来进行测试.开发.监控.增强和系统发布.AI ... 
- 反向代理后 location 被替换成本机域名。
			反向代理后 location 被替换成本机域名. 和上次写博客系统遇到的问题一样. 反向代理后,系统header中的location参数 域名自动被替换成本机域名了,本地测试没有问题,服务器反向代理就 ... 
- 【SpringCloud】Ribbon
			Ribbon 负载均衡原理 order-service 发起 user-service 请求,被ribbon进行拦截; ribbon会向注册中心拉取user-service 相对应的服务; 注册中心返 ... 
- 因为一条DDL,差点搞挂整个系统,这次真的长了教训
			有一次在线上提了一个sql变更,就是下面这条, -- 修改字段的数据类型由varchar(500)变更为text ALTER TABLE t MODIFY COLUMN name text; 提完之后 ... 
- VueJs禁止页面鼠标右键、选中、调用开发者工具
			1.禁止鼠标右键操作 // 禁止鼠标右键 window.oncontextmenu = function () { return false; }; 2.禁止选中网页内容 // 禁止选中网页上内容 w ... 
- 2023-07-13 C#深拷贝功能以及推荐使用方式
			C#深拷贝功能以及推荐使用方式 [作者]长生 深拷贝 深拷贝是用于在对引用对象进行复制时的一种操作方式.平常我们新建一个对象,然后直接赋值,只是对地址引用的赋值,在修改新建的对象时,也会对我们复制的对 ... 
- 2021-7-7 VUE动态样式
			Vue的动态样式实例1 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> & ... 
- npm install -g 错误集锦
			1.切换源安装:npm config set registry http://registry.cnpmjs.org,参考http://yijiebuyi.com/blog/b12eac891cdc5 ... 
- 仅三天,我用 GPT-4 生成了性能全网第一的 Golang Worker Pool,轻松打败 GitHub 万星项目
			目录 1. 我写了一个超牛的开源项目 1.1 你看看这性能 1.2 你看看这功能 1.3 你猜我这一百天都经历了啥 2. 你有多久没写并发程序了? 3. 问:一个 Worker Pool 程序需要包含 ... 
- 《深入理解Java虚拟机》笔记:垃圾收集算法和HotSpot的算法实现
			一.垃圾收集算法 由于垃圾收集算法的实现涉及大量的程序细节,而且各个平台的虚拟机操作内存的方法又各不相同,因此本节不打算过多地讨论算法的实现,只是介绍几种算法的思想及其发展过程. 垃圾收集算法概要 1 ... 
