整体二分小结
整体二分还是太牛了。
让我们来速通整体二分(逃)
让我来随便找一个人的 整体二分学习笔记
I.P3834 【模板】可持久化线段树 2
题目名不重要啊,其实可以整体二分的。
先来介绍整体二分的一种形式,我们开 \(n\) 个结构体,然后存 \(l,r,k,L,R,mid\),表示区间 \(l,r\) 找第 \(k\) 小,然后目前二分的左右端点和 \(mid\)。
我们把 \(n\) 个数看为加入,然后赋值为 \(0,0,0,a_i,a_i,a_i\),然后直接按 \(mid\) 排序,然后 \(mid\) 一样优先执行 \(l=0\) 也就是加入操作,然后拿树状数组维护一下即可。
大概就是你把 \(<mid\) 的试为 \(1\),其它的视为 \(0\),然后此时再来求区间和,跟 \(k\) 比较,然后看更新答案范围。
复杂度分析,一共进行 \(\log\) 轮,每次进行 \(n+q\) 次树状数组加入或查询,复杂度 \(\log^2\)。
还有一种写法是直接分治,其实本质上是一样的,就是 solve(S,l,r) 表示确定了集合 \(S\) 的答案在 \(l,r\) 内,然后去解决,不过这样只需要保证开始时有序的,接下来分开也是有序的,省去了每次排序的复杂度。
II.P2617 Dynamic Rankings
仿照上文,把修改改为加入和删除两个操作就好了。
当时我看到题解这样写都蒙了原来是默认读者已经完成了上一道题。
好了我们速通了,快去使用吧(doge)
就这样结束还是太水了,还是在加一点题吧。
III.P3527 [POI 2011] MET-Meteors
首先二分是很显然的吧,即使是暴力二分也容易一些?
并没有强制在线,直接考虑整体二分,把区间加改为差分,然后每次查询直接暴力看这个国家的所有太空站的前缀和即可,复杂度 \(O\left(n\log^2n\right)\)。
不过感觉是有点浪费吧,我们明明是在所有操作结束后再看,能不能通过一个预处理的形式来优化复杂度呢?
暴力求一遍前缀能做到单次 \(O\left(n\right)\),其它都可以 \(O\left(1\right)\),但总复杂度很劣。
注意到没必要这么前缀和把,我们考虑把所有合法操作和询问按 \(x\) 排序,把操作拆为 \(-1\) 和 \(1\),这样复杂度大概也是 \(n\log^2n\) 的?
回顾第一题的第二种做法,我们考虑在开始前就排序,然后每次分开后还是有序的,复杂度直接做到了 \(n\log n\),是不是很厉害(
一些坑点:请仔细读题,可能有 \(l>r\) 的情况,因为是个环。然后可能有国家根本没有太空站,还有就是此题轻微卡空间,#define int long long 然后空间又大的有福了。
code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
namespace IO
{
template<typename T>
void read(T &_x){_x=0;int _f=1;char ch=getchar();while(!isdigit(ch)) _f=(ch=='-'?-1:_f),ch=getchar();while(isdigit(ch)) _x=_x*10+(ch^48),ch=getchar();_x*=_f;}
template<typename T,typename... Args>
void read(T &_x,Args&...others){Read(_x);Read(others...);}
const int BUF=20000000;char buf[BUF],top,stk[32];int plen;
#define pc(x) buf[plen++]=x
#define flush(); fwrite(buf,1,plen,stdout),plen=0;
template<typename T>inline void print(T x){if(!x){pc(48);return;}if(x<0) x=-x,pc('-');for(;x;x/=10) stk[++top]=48+x%10;while(top) pc(stk[top--]);}
}
using namespace IO;
const int N = 3e5+10;
const ll inf = 1e10;
int n,m,k,p[N],a[N],cnt,x,y,z,l,r,ans[N],v1[N];
int ly[N],cnt1;
ll sum,v[N];
struct w
{
int op,x,id,z;
}b[N<<2],c[N<<2],d[N<<2];
inline bool cmp(w x,w y)
{
if(x.x == y.x) return x.op > y.op;//值一样先跑操作而非询问
return x.x < y.x;
}
void solve(int l,int r,int L,int R)
{
if(l > r) return;
if(L == R)
{
for(int i = l;i <= r;i++)
if(b[i].op == 0) ans[b[i].id] = L;
return;
}
int mid = ((L+R)>>1);
int tot = 0,tot1 = 0; sum = 0;
for(int i = l;i <= r;i++)//已经按x排序了
if(b[i].op == 0) v[b[i].id] = min(v[b[i].id]+sum,inf);
else if(b[i].id <= mid) sum += b[i].z;//仅考虑时间<=mid的
cnt1 = 0;
for(int i = l;i <= r;i++)
if(b[i].op == 0 && !v1[b[i].id]) v1[b[i].id] = 1,ly[++cnt1] = b[i].id;
for(int i = 1;i <= cnt1;i++)
{
if(v[ly[i]] >= p[ly[i]]) v1[ly[i]] = 1;//达标了
else v1[ly[i]] = 2,p[ly[i]] -= v[ly[i]];//减去这边,然后去跑mid+1那边的
v[ly[i]] = 0;
}
for(int i = l;i <= r;i++)
if(b[i].op == 0)
{
if(v1[b[i].id] == 1) c[++tot] = b[i];
else d[++tot1] = b[i];
}
else if(b[i].id <= mid) c[++tot] = b[i];
else d[++tot1] = b[i];
for(int i = 1;i <= cnt1;i++) v1[ly[i]] = 0;
for(int i = 1;i <= tot;i++) b[l+i-1] = c[i];
for(int i = 1;i <= tot1;i++) b[l+tot+i-1] = d[i];
solve(l,l+tot-1,L,mid),solve(l+tot,r,mid+1,R);
}
signed main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
read(n),read(m);
for(int i = 1;i <= m;i++) read(a[i]),b[++cnt].x = i,b[cnt].id = a[i];
for(int i = 1;i <= n;i++) read(p[i]);
read(k);
for(int i = 1;i <= k;i++)
{
read(l),read(r),read(x);
if(l <= r)//分讨加入
{
b[++cnt].op = 1,b[cnt].x = l,b[cnt].z = x,b[cnt].id = i;
b[++cnt].op = 1,b[cnt].x = r+1,b[cnt].z = -x,b[cnt].id = i;
}
else
{
b[++cnt].op = 1,b[cnt].x = l,b[cnt].z = x,b[cnt].id = i;
b[++cnt].op = 1,b[cnt].x = 1,b[cnt].z = x,b[cnt].id = i;
b[++cnt].op = 1,b[cnt].x = r+1,b[cnt].z = -x,b[cnt].id = i;
}
}//0查询,1操作
sort(b+1,b+1+cnt,cmp);
solve(1,cnt,1,k+1);
for(int i = 1;i <= n;i++)//注意可能有国家根本没有太空站
if(ans[i] == k+1 || ans[i] == 0) pc('N'),pc('I'),pc('E'),pc('\n');
else print(ans[i]),pc('\n');
flush();
return 0;
}
/*
离线,整体二分
至于一个国家多个太空站,小问题
理解为n个,复杂度还是nlog^2的
考虑查分,l+,r+1处-
然后就是前缀查,树状数组维护即可
注意先跑左边,跑完后不要撤销贡献
再开始排序可以做到单log,就不需要树状数组了
*/
浙公网安备 33010602011771号