CF1422F - Boring Queries、Gym102979C - Colorful Squares

CF1422F - Boring Queries

翻译

题解

由于答案太大需要取模,显然不能用普通的求 gcd ⁡ \gcd gcd 的方法来求 l c m \rm lcm lcm,所以只能质因数分解,然后对每个质因数的次数取 max ⁡ \max max

200000以内的质数有17984个,太多了,但是 200000 ≈ 447 \sqrt{200000}\approx 447 200000 447 以内的质数很少,只有86个。所以我们可以对于这一部分质数,每一种维护一颗区间最大值的线段树(不用动态开点),询问一次的时间就是 O ( 86 ∗ log ⁡ n ) O(86*\log n) O(86logn) ,可以用zkw卡卡常。

对于大于447的质因子,每个 a i a_i ai 最多只有次数为1的一个,次数取 max ⁡ \max max 只可能是0或1,所以只需要把区间内这样的质因数去重后乘起来。这是一个经典的颜色去重求和的问题。

这个问题可以很好地用莫队离线解决,可惜这题强制在线。在线怎么做呢?其实可以比离线莫队更快。我们规定只有区间内某种颜色(质因数)出现的第一个位置被算入贡献,也就是说,设 p r e i pre_i prei 表示 i i i 位置前面第一个和它颜色相同的位置,对于区间 [ l , r ] [l,r] [l,r],只有满足 p r e i < l , l ≤ i ≤ r pre_i<l,l\le i\le r prei<l,lir 时才会算入贡献。

还是用线段树,每个区间维护这个区间内所有位置按 p r e pre pre 从小到大排序的序列, p u s h u p \rm pushup pushup 时就归并一下。查询的时候,对于每个区间,二分找到最后一个 p r e < l pre<l pre<l 的位置,乘上前缀积即可,所以前面还要维护前缀积。查询一次是 O ( log ⁡ 2 n ) O(\log^2n) O(log2n)

也可以想想主席树和简单分块的做法,同样用到了 p r e pre pre 值。

代码

#include<cstdio>//JZM yyds!!!
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<ctime>
#include<vector>
#include<string>
#include<queue>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define uns unsigned
#define MAXN 100005
#define MAXM 200005
#define INF 1e18
#define lowbit(x) ((x)&(-(x)))
#define MOD 1000000007ll
#define IF it->first
#define IS it->second
#define rg register
using namespace std;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
	return f?x:-x;
}
inline ll ksm(ll a,ll b,ll mo){
	ll res=1;
	for(;b;b>>=1,a=a*a%mo)if(b&1)res=res*a%mo;
	return res;
}
int n,B,Q,m,k;
ll b[MAXN<<4],a[MAXN],bg[MAXN],mx;
map<int,int>mp;
map<int,int>::iterator it;
bool nop[MAXM];
vector<int>pz[MAXM],z[MAXN];
inline void init(int n){
	nop[0]=nop[1]=1;
	for(int i=2;i<=n;i++)
		if(!nop[i]){
			pz[i].push_back(i);
			for(int j=(i<<1);j<=n;j+=i)
				pz[j].push_back(i),nop[j]=1;
		}
}

int p;
int gp[MAXN],pre[MAXN];
vector<int>f[MAXN*3];//这个部分还是忍不住用zkw
vector<ll>ml[MAXN*3];
inline vector<int> merg(vector<int>&a,vector<int>&b){
	vector<int>r;
	int la=a.size(),lb=b.size(),u=0,v=0;
	while(u<la&&v<lb){
		if(pre[a[u]]<=pre[b[v]])r.push_back(a[u]),u++;
		else r.push_back(b[v]),v++;
	}
	while(u<la)r.push_back(a[u]),u++;
	while(v<lb)r.push_back(b[v]),v++;
	return r;
}
inline void build(){
	for(rg int i=1;i<=n;i++)
		f[p+i].clear(),f[p+i].push_back(i);
	for(rg int i=p-1;i>0;i--)
		f[i]=merg(f[i<<1],f[i<<1|1]);
	for(int i=p+n;i>0;i--){
		ml[i].clear();
		if(f[i].empty())continue;
		ml[i].push_back(b[bg[f[i][0]]]);
		for(uns j=1;j<f[i].size();j++)
			ml[i].push_back(ml[i][j-1]*b[bg[f[i][j]]]%MOD);
	}
}
inline ll sch(int l,int r){
	ll res=1;int pr=l;
	for(l=p+l-1,r=p+r+1;l^1^r;l>>=1,r>>=1){
		if(~l&1){
			int s=-1,le=f[l^1].size();
			for(rg int i=17;i>=0;i--){
				int o=s+(1<<i);
				if(o>=le||pre[f[l^1][o]]>=pr)continue;
				s=o;
			}
			if(s>=0)res=res*ml[l^1][s]%MOD;
		}
		if(r&1){
			int s=-1,le=f[r^1].size();
			for(rg int i=17;i>=0;i--){
				int o=s+(1<<i);
				if(o>=le||pre[f[r^1][o]]>=pr)continue;
				s=o;
			}
			if(s>=0)res=res*ml[r^1][s]%MOD;
		}
	}
	return res;
}

struct zkw{
	int f[MAXN*3];
	inline void add(int x,int d){
		for(f[p+x]=max(f[p+x],d),x=(p+x)>>1;x;x>>=1)
			f[x]=max(f[x<<1],f[x<<1|1]);
	}
	inline int query(int l,int r){
		int res=0;
		for(l=p+l-1,r=p+r+1;l^1^r;l>>=1,r>>=1){
			if(~l&1)res=max(res,f[l^1]);
			if(r&1)res=max(res,f[r^1]);
		}
		return res;
	}
}qk[90];
signed main()
{
	n=read();
	for(p=1;p<n+2;p<<=1);
	for(int i=1;i<=n;i++)a[i]=read(),mx=max(mx,a[i]);
	init(mx),B=ceil(sqrt(mx));
	for(int i=1;i<=n;i++){
		int c=a[i];
		for(uns j=0;j<pz[c].size();j++){
			if(pz[c][j]<=B)z[i].push_back(pz[c][j]);
			else bg[i]=pz[c][j];
		}
		if(bg[i])mp[bg[i]]=1;
		for(uns j=0;j<z[i].size();j++)
			mp[z[i][j]]=1;
	}
	for(it=mp.begin();it!=mp.end();it++)IS=++m,b[m]=IF;
	b[0]=1;
	for(int i=1;i<=m;i++){if(b[i]>B)break;k=i;}
	for(int i=1;i<=n;i++){
		if(bg[i])bg[i]=mp[bg[i]];
		for(uns j=0;j<z[i].size();j++){
			int o=z[i][j],x=a[i],mt=0;
			z[i][j]=mp[z[i][j]];
			while(x%o==0)x/=o,mt++;
			qk[z[i][j]].add(i,mt);
		}
	}
	for(int i=1;i<=n;i++){
		if(!bg[i])continue;
		pre[i]=gp[bg[i]],gp[bg[i]]=i;
	}
	build();
	ll las=0;
	Q=read();
	while(Q--){
		int l=read(),r=read();
		l=(las+l)%n+1,r=(las+r)%n+1;
		if(l>r)swap(l,r);
		ll as=1;
		for(int i=1;i<=k;i++){
			int nm=qk[i].query(l,r);
			as=as*ksm(b[i],nm,MOD)%MOD;
		}
		as=as*sch(l,r)%MOD;
		printf("%lld\n",as),las=as;
	}
	return 0;
}

Gym102979C - Colorful Squares

翻译:平面内有𝑛个点,一共有𝑘种颜色。其中,第𝑖个点的坐标是( 𝑥 i 𝑥_i xi , 𝑦 i 𝑦_i yi),颜色是 𝑐 i 𝑐_i ci
求一个边长最小的正方形,使得它覆盖的点的颜色有𝑘种。注意正方形的边长可能为0。

题解

如果探索正解的时间足够的话,记住这种题目二分答案+扫描线是应有的直觉。

x x x 坐标从小到大枚举长度为 m i d mid mid 的区间,通过加入、删除维护区间内的点,剩下的事就是判断在 y y y 坐标上有没有一个长度为 m i d mid mid 的区间包含所有颜色的点。转换一下,就是询问是否存在一个位置 b b b,使得 y y y 坐标在 [ b − m i d , b ] [b-mid,b] [bmid,b] 以内的点的颜色种类数为 k k k

我们可以对于每一种颜色,用 m u l t i s e t \rm multiset multiset 维护每个点后面的第一个颜色相同的点。设位置 i i i 纵轴上后面第一个颜色相同的点位置为 s u f i suf_i sufi,那么每个点 i i i 对区间 [ y i , min ⁡ ( y s u f i − 1 , y i + m i d ) ] [y_i,\min(y_{suf_i}-1,y_i+mid)] [yi,min(ysufi1,yi+mid)] 有1的贡献,用线段树维护最大值即可。

时间复杂度 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)

代码

#include<cstdio>//JZM yyds!!!
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<ctime>
#include<vector>
#include<string>
#include<queue>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define uns unsigned
#define MAXN 100005
#define MAXM 250005
#define INF 1e18
#define lowbit(x) ((x)&(-(x)))
#define MOD 998244353ll
#define IF it->first
#define IS it->second
using namespace std;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
	return f?x:-x;
}
int n,k;
struct itn{
	int x,y,c;itn(){}
	itn(int X,int Y,int C){x=X,y=Y,c=C;}
}a[MAXN];
inline bool cmp(itn a,itn b){return a.x<b.x;}
multiset<int>st[MAXN];
multiset<int>::iterator it;
int p,m,f[MAXM*3],lz[MAXM*3];
inline void add(int l,int r,int d){
	if(l>r||l<1)return;
	for(l=p+l-1,r=p+r+1;l^1^r;){
		if(~l&1)f[l^1]+=d,lz[l^1]+=d;
		if(r&1)f[r^1]+=d,lz[r^1]+=d;
		l>>=1,r>>=1;
		f[l]=max(f[l<<1],f[l<<1|1])+lz[l];
		f[r]=max(f[r<<1],f[r<<1|1])+lz[r];
	}
	for(l>>=1;l;l>>=1)
		f[l]=max(f[l<<1],f[l<<1|1])+lz[l];
}
inline void addp(int c,int d,int md){
	it=st[c].lower_bound(d);
	if(*it==d){st[c].insert(d);return;}
	int s=*it;it--;
	add(*it,min(*it+md,s-1),-1);
	add(*it,min(*it+md,d-1),1);
	add(d,min(d+md,s-1),1);
	st[c].insert(d);
}
inline void delp(int c,int d,int md){
	st[c].erase(st[c].find(d));
	it=st[c].lower_bound(d);
	if(*it==d)return;
	int s=*it;it--;
	add(*it,min(*it+md,d-1),-1);
	add(d,min(d+md,s-1),-1);
	add(*it,min(*it+md,s-1),1);
}
inline bool check(int md){
	for(int i=1;i<=k;i++)
		st[i].clear(),st[i].insert(m+1),st[i].insert(0);
	for(int i=p+m;i>0;i--)f[i]=lz[i]=0;
	for(int i=1,j=1;i<=n;i++){
		addp(a[i].c,a[i].y,md);
		while(j<i&&a[j].x<a[i].x-md)
			delp(a[j].c,a[j].y,md),j++;
		if(f[1]>=k)return 1;
	}
	return 0;
}
signed main()
{
	n=read(),k=read();
	for(int i=1;i<=n;i++){
		a[i].x=read(),a[i].y=read(),a[i].c=read();
		m=max(m,a[i].y);
	}
	sort(a+1,a+1+n,cmp);
	for(p=1;p<m+2;p<<=1);
	int l=0,r=250000,mid;
	while(l<r){
		mid=(l+r)>>1;
		if(check(mid))r=mid;
		else l=mid+1;
	}
	printf("%d\n",r);
	return 0;
}

总结

为什么把这两道题写在一起,就是因为它们都用到了同样的技巧:利用同颜色前驱后继。用到这个技巧的题还有[APIO2018]新家

这个技巧通用性非常高,用来处理多种颜色去重一类的问题。很多用到这个技巧的题目,常常被尊为紫题或黑题。如果熟练运用这个技巧,这些题目都会变得异常简单。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值