减半警报器

lxl 的神秘 trick。
例题:[THUPC2021] 鬼街 Incoming Asteroids
它们一般都有一个共性:维护一些集合,每次可以对一个集合内的数同时减一个非负数,求每个集合所有数均不为正数的最小时间(警报器在这最小时间时会产生一次警报)。
由于鸽巢原理,可以把一个警报器 \(\left(x,y\right)\) 拆到集合 \(x\) 内的每一个数 \(k\) 中,并检测 \(k\) 的权值是否减去了 \(\left\lceil\frac{y}{k}\right\rceil\)。可以发现小警报器报警是大警报器报警的必要条件。
每次修改时,就对它的集合内的所有数查看监视它的小警报器有没有发生警报。如果有,就把它撤走,并且把小警报器所在的大警报器的其它所有小警报器的 \(y\) 值重分配。
可以使用堆来维护小警报器,以它们的 \(y\) 值作为先后顺序。
可以证明这样做,一个大小为 \(s\) 的集合中的报警器 \((x,y)\) 至多会被重分配 \(\log_{\frac{s}{s-1}}y\) 次。

对于鬼街,容易发现对于 \(10^5\) 范围内的数 \(n\),它的本质不同质因子个数 \(\omega(n)\) 最多为 \(6\),所以直接照上面做即可。
勉强卡过。代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,int> P;
typedef tuple<int,ll,int> Q;

char buf[1<<21],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
template<typename T>
inline void rd(T &a){
	a=0;
	char c=getchar();
	while(!isdigit(c))c=getchar();
	while(isdigit(c))a=(a<<3)+(a<<1)+(c^48),c=getchar();
}

const int N=1e5+5;
int n,m;
ll cnt[N];//累计次数 
int idi,xx[N];ll yy[N];//维护警报器 

vector<int>pri[N];//本质不同质因子(1e5范围内最多6个) 

vector<int>ans;
int beeped[N];//已报警过,不计入答案 
priority_queue<P,vector<P>,greater<P> >q[N];//每个房屋上安装的小报警器 
vector<Q>obs;
int last;

void check(int u){
	if(beeped[u])return;
	ll v=yy[u];
	for(int i:pri[xx[u]])
		v-=cnt[i];
	if(v<=0)return (void)(ans.push_back(u),beeped[u]=true);
	int siz=pri[xx[u]].size();
	for(int i:pri[xx[u]])
		obs.push_back(make_tuple(i,(v-1)/siz+cnt[i]+1,u));//鸽巢原理 
}

void upd(){
	stable_sort(obs.begin(),obs.end());
	int l=unique(obs.begin(),obs.end())-obs.begin();//去重 
	for(int i=0;i<l;i++)
		q[get<0>(obs[i])].push(make_pair(get<1>(obs[i]),get<2>(obs[i])));
	obs.clear();
}//更新小警报器 

void add(int x,ll y){
	cnt[x]+=y;
	while(!q[x].empty()){
		P u=q[x].top();
		if(u.first>cnt[x])break;
		q[x].pop(),check(u.second);//重分配 
	}
	upd();
}

signed main(){
	
	rd(n),rd(m);
	
	for(int i=2;i<=n;i++)if(pri[i].empty())
		for(int j=i;j<=n;j+=i)pri[j].push_back(i);
	
	while(m--){
		int op,x;ll y;rd(op),rd(x),rd(y);y^=last;
		if(!op){
			for(auto i:pri[x])add(i,y);
			sort(ans.begin(),ans.end());
			cout<<(last=ans.size())<<" ";
			for(int i:ans)cout<<i<<" ";
			cout<<"\n";
			ans.clear();
		}
		else{
			xx[++idi]=x,yy[idi]=y;
			for(int i:pri[x])yy[idi]+=cnt[i];
			check(idi);
			upd();
		}
	}
	
	return 0;
}
posted @ 2024-06-07 11:23  yzy4090  阅读(291)  评论(1)    收藏  举报  来源