[集训队互测2016] Unknown

经典题,国赛前才做怎么回事。

一句话题意:末尾加删,区间询问凸包信息。

一个做法是建出操作树,发现本题相当于路径查询凸包信息。于是可以树剖/点分治。点分治的话可以转化成只有前缀询问的情况用平衡树维护图报加入一个点和回退。但是这样太难写了!观察到询问只有直上直下的链(当然如果不是可以拆成两条这样的链)。我们点分治时特殊处理当前分治连通块中最浅的点所在的子连通块,发现所有经过中心的路径在这个子连通块中的部分都是根到重心的链,所以只需要求动态加入一个点的凸包可以 CDQ/平衡树维护。路径剩下的部分递归下去做就行了。算法离线,空间复杂度 O(n)

另一个做法在线,不过空间复杂度有一个 log。考虑只加怎么做,我们可以二进制分组。这个题是区间询问,所以得维护一个线段树的结构,然后每次往后加,如果线段树上节点加满了就 pushup 求出这个节点的信息,由于区间询问不会访问没有加满的点所以这样做就是对的。

有删除操作呢?我们思考二进制分组的原理很类似一个二进制计数器,每次给这个数 +1 暴力进位的摊还代价是 O(1)。现在有删除操作相当于有 1 操作。如果你在一个势能(末尾的 1 的个数很多)的点反复 ±1 复杂度就会爆炸。此时我们想到了我们解决整数一题中使用的 Trygub Number Trick。即在 B 进制下允许一位的值可以是 (B,B) 中间的数。

Trygub Number 对应着这样一种思想:允许少数地方在删除若干次后保持这样的不合法状态。避免在加删操作都有的时候一个势能大的状态“过于敏感”。即让一个势能大的状态操作释放势能一次之后不会立马操作到另一个大势能状态。(讲得好抽象 QwQ)

这道题中我们可以这样干:允许每一层最后一个满了的区间还没有求出信息,查到这样不合法的节点时暴力递归下去,这样查询仍然只会访问 log 个节点。而每一层一旦发生了一次 pushup 就必须要再操作区间长度次才会发生另一次 pushup

这个技巧明显很好扩展,比如双端插入删除也可以使用这个技巧。

两个时间复杂度都是两个 log,后者常数明显更小,我实现了后者。(但是我写的代码常数很大 QwQ)

#include <cstdio>
#include <algorithm>
#define lc (p<<1)
#define rc (p<<1|1)
using namespace std;
int read(){
char c=getchar();int x=0;bool f=0;
while(c<48||c>57) f|=(c=='-'),c=getchar();
do x=x*10+(c^48),c=getchar();
while(c>=48&&c<=57);
if(f) return -x;
return x;
}
typedef long long ll;
const int W=1<<20,N=500003,P=998244353;
const ll INF=0x3f3f3f3f3f3f3f3f;
struct node{
int x,y;
friend bool operator<(const node a,const node b){
if(a.x^b.x) return a.x<b.x;
return a.y<b.y;
}
};
bool ava[W];node *nd[W];
int dlt,bt,m,len;
int sz[W];
node arr[N];
node stk[N];int tp;
inline bool check(node a,node b,node c){
return (ll)(b.y-a.y)*(c.x-b.x)<=(ll)(c.y-b.y)*(b.x-a.x);
}
inline void pushup(int p){
if(ava[p]) return;
ava[p]=1;
merge(nd[lc],nd[lc]+sz[lc],nd[rc],nd[rc]+sz[rc],arr);
tp=0;
for(int i=0;i<sz[lc]+sz[rc];++i){
while(tp>1&&check(stk[tp-1],stk[tp],arr[i])) --tp;
stk[++tp]=arr[i];
}
nd[p]=new node[sz[p]=tp];
copy(stk+1,stk+tp+1,nd[p]);
}
ll query(int l,int r,int x,int y,int p=1,int d=0){
int L=(p<<(bt-d))-dlt;
int R=L+(1<<(bt-d))-1;
if(r<L||l>R) return -INF;
if(ava[p]&&l<=L&&R<=r){
int l=0,r=sz[p]-1;
while(l<r){
int md=(l+r)>>1;
if((ll)y*(nd[p][md+1].x-nd[p][md].x)<(ll)(nd[p][md+1].y-nd[p][md].y)*x)
l=md+1;
else r=md;
}
return (ll)x*nd[p][r].y-(ll)y*nd[p][r].x;
}
return max(query(l,r,x,y,rc,d+1),query(l,r,x,y,lc,d+1));
}
void solve(){
len=0;
for(dlt=1,bt=0;dlt<=m;dlt<<=1,++bt);
int res=0;
while(m--){
int op=read();
if(op==1){
int p=dlt+(len++),d=bt;
ava[p]=1;
nd[p]=new node[sz[p]=1];
nd[p]->x=read();
nd[p]->y=read();
while(p>1){
p>>=1;--d;
if(((p+1)<<(bt-d))>dlt+len||p==(1<<bt)) break;
pushup(p-1);
}
}
if(op==2){
int p=dlt+(--len);
ava[p]=0,delete[] nd[p];
while(p>1){
p>>=1;
if(ava[p]) ava[p]=0,delete[] nd[p];
}
}
if(op==3){
int l=read()-1,r=read()-1,x=read(),y=read();
res^=(query(l,r,x,y)%P+P)%P;
}
}
printf("%d\n",res);
for(int i=1;i<dlt*2;++i) if(ava[i]) ava[i]=0,delete[] nd[i];
}
int main(){
read();m=read();
while(m) solve(),m=read();
return 0;
}
posted @   yyyyxh  阅读(72)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示