减半警报器
一个很厉害的 Trick,一般通过将信息均摊至若干部分,以一只
- P7603 [THUPC2021] 鬼街
有
n 个城市,从1\sim n 编号,共有m 次操作:
- 加入一个监视
x 所有质因子对应城市的监视器,阈值为y 。- 在
x 所有质因子对应城市内发生y 次事件。请在每次事件后输出恰好达到阈值的监视器编号。
1\le n,m \le 10^5
比较经典的减半情报器。
发现
接下来一个比较厉害的处理是,若
这显然是可以实现的,考虑时间复杂度为什么是对的。
总时间复杂度
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
const int maxn=100010;
il ll read(){
ll x=0;
char c=getchar();
for(;!(c>='0'&&c<='9');c=getchar());
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+c-'0';
return x;
}
struct info{
ll x,y;
info (ll _x=0,ll _y=0){x=_x,y=_y;}
}q[maxn];
int n,m,N,cnt;
int p[maxn],num[maxn];
vector<int>Div[maxn];
bool vis[maxn];
void Oula(int n){
for(int i=2;i<=n;i++){
if(!vis[i]) p[++cnt]=i,num[i]=cnt;
for(int j=1;j<=cnt&&i*p[j]<=n;j++){
vis[i*p[j]]=1;
if(i%p[j]==0) break;
}
}
}
void init(){
Oula(n);
for(int i=1;i<=n;i++){
int x=i,qx=sqrt(x);
for(int j=1;p[j]<=qx;j++)
if(x%p[j]==0){
Div[i].push_back(j);
while(x%p[j]==0) x/=p[j];
}
if(x>1)Div[i].push_back(num[x]);
}
}
int rt[maxn];
int st[maxn],top;
namespace Seg{
#define MAXN maxn*100
int ls[MAXN],rs[MAXN],cnt;
ll dm[MAXN],id[MAXN],lz[MAXN];
void pushup(int i){
if(!id[ls[i]]) id[i]=id[rs[i]],dm[i]=dm[rs[i]];
else if(!id[rs[i]]) id[i]=id[ls[i]],dm[i]=dm[ls[i]];
else if(dm[ls[i]]<dm[rs[i]]) id[i]=id[ls[i]],dm[i]=dm[ls[i]];
else if(dm[ls[i]]>=dm[rs[i]]) id[i]=id[rs[i]],dm[i]=dm[rs[i]];
}
void pushdown(int i){
if(!lz[i]) return ;
if(id[ls[i]]) dm[ls[i]]+=lz[i],lz[ls[i]]+=lz[i];
if(id[rs[i]]) dm[rs[i]]+=lz[i],lz[rs[i]]+=lz[i];
lz[i]=0;
}
void Am(int &i,int l,int r,int x,ll k){
if(!i) i=++cnt;
if(l==r) return dm[i]=k,id[i]=l,void();
int mid=l+r>>1;
pushdown(i);
if(mid>=x) Am(ls[i],l,mid,x,k);
else Am(rs[i],mid+1,r,x,k);
pushup(i);
}
ll Q(int i,int l,int r,int x){
// printf("!!!%d\n",x);
if(l==r) return dm[i];
int mid=l+r>>1;
pushdown(i);
if(mid>=x) return Q(ls[i],l,mid,x);
else return Q(rs[i],mid+1,r,x);
}
void Dm(int i,int l,int r,int x){
if(l==r) return id[i]=0,void();
int mid=l+r>>1;
pushdown(i);
if(mid>=x) Dm(ls[i],l,mid,x);
else Dm(rs[i],mid+1,r,x);
pushup(i);
}
void D(int i,ll k){
if(!i) return ;
if(id[i]) dm[i]-=k,lz[i]-=k;
pushdown(i);
}
#undef MAXN
}
void Machine(int t,int x,ll y){
if(y<0){
for(int d:Div[x])
Seg::Dm(rt[d],1,m,t);
st[++top]=t;
return ;
}
for(int i=0,len=Div[x].size();i<len;i++)
if(i<y%len) Seg::Am(rt[Div[x][i]],1,m,t,y/len+1);
else Seg::Am(rt[Div[x][i]],1,m,t,y/len);
}
int main(){
n=read(),m=read(),init();
int lt=0,x,opt,t; ll y;
for(int Case=1;Case<=m;Case++){
opt=read(),x=read(),y=read()^lt;
if(opt) --y,q[++N]=info(x,y),Machine(N,x,y);
else{
for(int d:Div[x]){
Seg::D(rt[d],y);
// printf("D : %d,%d\n",d,y);
// printf("!!!!%d,%d\n",Seg::dm[rt[d]],Seg::id[rt[d]]);
while(Seg::dm[rt[d]]<0&&Seg::id[rt[d]]){
t=Seg::id[rt[d]],q[t].y=0;
for(int g:Div[q[t].x])
q[t].y+=Seg::Q(rt[g],1,m,t);
// printf("cg : %d[%d]\n",t,q[t].y);
Machine(t,q[t].x,q[t].y);
}
}
printf("%d ",lt=top);
sort(st+1,st+1+top);
for(int i=1;i<=top;i++)
printf("%d ",st[i]);
printf("\n"),top=0;
}
}
return 0;
}
- gym 的题,题号忘了,有时间补。
有
n 个人,从1\sim n 编号,第i 个人可以被选中当且仅当区间[l_i,r_i] 中被选中的人\ge v_i 。求最终哪些人被选中。1\le n,m \le 10^5
也是很经典的减半警报器模型。
发现问题等价于给定
其实就是把上一道题的
区间问题,去除限制基本都是去掉一个端点。因此可以考虑找到一个位置
维护后的操作也是一样,若有均摊下去的区间权值
但是注意到上文的
总时间复杂度
代码有时间再补。傻逼文化课。