#23 CF1285F & CF1588F & CF1515I

Classical?

题目描述

点此看题

解法

感觉被题目名称深深嘲讽了一波,这么经典我都不会做

思考简化的问题,假设加上 \(\gcd (a_i,a_j)=1\) 怎么做?由于 \(\tt lcm\) 的形式都是 \(x\times y\),我们利用偏序关系来少枚举一些东西。我们先把 \(a\) 数组从大到小排序,然后依次扫描它的每一个元素。

考虑我们只需要找到最大的与它互质的数即可,但貌似这并不好找。一个关键的 \(\tt observation\) 是:设现在扫到的数为 \(x\),最大与它互质的数是 \(y\),那么对于所有 \(x<z\leq y\),这些 \(z\) 都可以不需要考虑了。

这是因为这些 \(z\) 后续产生的答案肯定不如 \(x\times y\) 优,那么我们可以暴力找这个 \(y\),用栈来维护,每次把栈顶弹掉直接找到 \(y\) 为止。复杂度是基于每个点只会入栈出栈一次,所以我们需要提前计算出栈中于 \(x\) 互质的数的个数,那么弹栈的时候就能恰到好处地停止。设 \(cnt(x)\) 表示栈中为 \(x\) 倍数的数,简单莫比乌斯反演可以得到:

\[\sum[\gcd(x,y)=1]=\sum_{d|x}\mu(d)\cdot cnt(d) \]

那么单独做一次时间复杂度 \(O(n\log n)\),枚举 \(\gcd\) 分别做,可以做到 \(O(n\log^2n)\)

有个很巧妙的优化:考虑一定存在 \(a|x,b|y\) 使得 \(a\cdot b=lcm(x,y)\) 并且 \(\gcd(a,b)=1\),那么我们把给定数的所有因数拿出来,只需要用上面的方法做一次即可,时间复杂度 \(O(n\log n)\)

总结

对答案 \(\tt roughly\) 的筛选是很重要的,通过粗略的偏序关系可以知道一些情况下有些东西不需要考虑。

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int M = 100005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,s[M],a[M],b[M];long long ans;
int cnt,p[M],vis[M],mu[M];vector<int> g[M];
void init(int n)
{
	mu[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(!vis[i]) p[++cnt]=i,mu[i]=-1;
		for(int j=1;j<=cnt && i*p[j]<=n;j++)
		{
			vis[i*p[j]]=1;
			if(i%p[j]==0) break;
			mu[i*p[j]]=-mu[i];
		}
	}
	for(int i=1;i<=n;i++)
		for(int j=i;j<=n;j+=i)
			g[j].push_back(i);
}
int gcd(int a,int b)
{
	return !b?a:gcd(b,a%b);
}
signed main()
{
	n=read();m=100000;init(m);
	for(int i=1;i<=n;i++)
	{
		int x=read();a[x]=1;
		ans=max(ans,1ll*x);
	}
	for(int i=1;i<=m;i++)
		for(int j=i;j<=m;j+=i)
			a[i]|=a[j];
	for(int i=m;i>=1;i--)
	{
		if(!a[i]) continue;
		int y=0;
		for(int x:g[i]) y+=mu[x]*b[x];
		while(y)
		{
			int j=s[k--];
			if(gcd(i,j)==1)
				ans=max(ans,1ll*i*j),y--;
			for(int x:g[j]) b[x]--;
		}
		for(int x:g[i]) b[x]++;
		s[++k]=i;
	}
	printf("%lld\n",ans);
}

Jumping Through the Array

题目描述

点此看题

解法

考虑询问分块,每 \(\sqrt n\) 次操作分成一块处理,处理完之后暴力重构。

发现这样会产生一些很好的性质,我们可以把涉及三操作的点全部标记出来。然后对于每个标记点,我们找到其极长空白前驱段进行染色,那么具有相同颜色的点就是一个等价类,可以缩成一个点,那么场上就只剩下了 \(O(\sqrt n)\) 个点。

那么现在二三操作都可以暴力进行,考虑一操作怎么做?

对于每个等价类考虑其对询问的贡献,可以预处理出前 \(i\) 个点中有多少个点在这个等价类中(注意 \(i\) 的取值也只有 \(O(\sqrt n)\) 个),那么差分一下就可以计算了。

时间复杂度 \(O(n\sqrt n)\),可以适当调调块长。

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 200005;
const int N = 1005;
#define ll long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,p[M],col[M],pre[M],vis[M],b[M];
int id[M],sz[N<<1][N<<1];ll s[M],a[M],ad[M];
struct node{int op,x,y;}q[M];
void color(int x,int c)
{
	for(;col[x]==0;x=pre[x]) col[x]=c;
}
void add(int x,int c)
{
	for(;!vis[x];x=col[p[x]]) ad[x]+=c,vis[x]=1;
	for(;vis[x]==1;x=col[p[x]]) vis[x]=0;
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=n;i++) pre[p[i]=read()]=i;
	m=read();
	for(int i=1;i<=m;i++)
		q[i].op=read(),q[i].x=read(),q[i].y=read();
	for(int l=1;l<=m;l+=N)
	{
		int r=min(l+N-1,m);k=0;
		for(int i=0;i<=n;i++)
			ad[i]=id[i]=col[i]=vis[i]=b[i]=0;
		for(int i=1;i<=n;i++)
			s[i]=s[i-1]+a[i];
		//color all nodes
		for(int i=l;i<=r;i++)
		{
			if(q[i].op==2) col[q[i].x]=q[i].x;
			if(q[i].op==3)
				col[q[i].x]=q[i].x,
				col[q[i].y]=q[i].y;
		}
		for(int i=1;i<=n;i++) if(col[i]==i)
			b[++k]=i,col[i]=0,color(i,i);
		//process all query I
		for(int i=l,t=0;i<=r;i++) if(q[i].op==1)
		{
			if(!id[q[i].x-1]) id[q[i].x-1]=++t;
			if(!id[q[i].y]) id[q[i].y]=++t;
		}
		for(int i=0;i<=n;i++)
		{
			if(i) ad[col[i]]++;
			if(id[i]) for(int j=1;j<=k;j++)
				sz[id[i]][j]=ad[b[j]];
		}
		for(int i=0;i<=n;i++) ad[i]=0;
		//brute simulate
		for(int i=l;i<=r;i++)
		{
			int op=q[i].op,x=q[i].x,y=q[i].y;
			if(op==1)
			{
				ll ans=s[y]-s[x-1];
				for(int j=1;j<=k;j++)
					ans+=ad[b[j]]*(sz[id[y]][j]
					-sz[id[x-1]][j]);
				printf("%lld\n",ans);
			}
			if(op==2) add(x,y);
			if(op==3)
			{
				swap(p[x],p[y]);
				pre[p[x]]=x;pre[p[y]]=y;
			}
		}
		for(int i=1;i<=n;i++) a[i]+=ad[col[i]];
	}
}

Phoenix and Diamonds

题目描述

点此看题

解法

显然的想法是线段树二分,我们按照预处理出的顺序,每次能取则取。但是这样会遇到一个问题:当遇到重量过大的物品时,我们可能会在这里停下,所以处理次数会大大增加。

那么有一个解决这个问题的想法是,我们按照某个标准把物品分成重物品和轻物品。这样可以把重物品特殊处理或者直接跳过,轻物品大段选取,可能可以优化停下来的次数。

理想的情况是我们只停下来 \(O(\log c)\) 次,那么我们直接套用二进制的方法。设 \(k\) 满足 \(2^{k-1}\leq c<2^k\),那么我们把重量 \(\in[2^{k-1},2^k)\) 的分为重物品,重量 \(<2^{k-1}\) 的分为轻物品。关键性质是选取一个重物品之后 \(k\) 会立即降低 \(1\)

现在的问题变成了快速找到第一个可以选取的重物品(或者判断找不到),设现在选取到的问题是 \(x\),某个重物品的位置是 \(y\),那么如果 \([x,y)\) 之间轻物品的重量和\(+\)这个重物品的重量 \(\leq c\),这就是可以选取的重物品。

具体来说,就是用线段树维护区间内的重物品,满足它到区间左端点的轻物品重量和\(+\)这个重物品的重量的最小值。那么我们要维护 \(sw[i][j],sv[i][j],ex[i][j]\) 分别表示 \(k=j\) 时,区间的轻物品重量和 \(/\) 价值和 \(/\) 最小重物品的权值。然后直接在线段树上搜即可,在买得起区间全部物品,或者是买不起区间重物品时退出。

时间复杂度 \(O(n\log n\log c)\),实现细节可以参考代码,十分好理解。

总结

这类多次询问,按一定顺序选物品的问题,考虑让某一量迅速的下降,可以主动套用二进制之类 \(O(\log)\) 的东西。

多个过程的线段树操作,代码实现中可以直接在线段树上搜索,恰当时机退出即可,代码会好写很多。

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 200005;
const int N = 800005;
#define int long long
const int inf = 1e18;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,w[M],v[M],a[M],p[M],id[M];
int sw[N][18],sv[N][18],ex[N][18];
void upd(int i,int x)
{
	for(int j=0;j<18;j++)
	{
		sw[i][j]=sv[i][j]=0;ex[i][j]=inf;
		if(w[x]<(1<<j))
			sw[i][j]+=a[x]*w[x],sv[i][j]+=a[x]*v[x];
		else if(w[x]<(1<<j+1) && a[x])
			ex[i][j]=w[x];
	}
}
void up(int i)
{
	for(int j=0;j<18;j++)
	{
		sw[i][j]=sw[i<<1][j]+sw[i<<1|1][j];
		sv[i][j]=sv[i<<1][j]+sv[i<<1|1][j];
		ex[i][j]=min(ex[i<<1][j],ex[i<<1|1][j]+sw[i<<1][j]);
	}
}
void add(int i,int l,int r,int id,int c)
{
	if(l==r) {a[p[l]]+=c;upd(i,p[l]);return ;}
	int mid=(l+r)>>1;
	if(mid>=id) add(i<<1,l,mid,id,c);
	else add(i<<1|1,mid+1,r,id,c);
	up(i);
}
int ask(int i,int l,int r,int &c)
{
	if(l==r)
	{
		int x=min(a[p[l]],c/w[p[l]]);
		c-=x*w[p[l]];return v[p[l]]*x;
	}
	int mid=(l+r)>>1;
	while(k && (1<<k-1)>c) k--;
	if(sw[i][k]<=c) return c-=sw[i][k],sv[i][k];
	if(sw[i][k-1]<=c && ex[i][k-1]>c)
		return c-=sw[i][k-1],sv[i][k-1];
	return ask(i<<1,l,mid,c)+ask(i<<1|1,mid+1,r,c);
}
signed main()
{
	n=read();m=read();
	for(int i=1;i<=n;i++)
		a[i]=read(),w[i]=read(),v[i]=read(),p[i]=i;
	sort(p+1,p+1+n,[&](int i,int j)
	{return v[i]==v[j]?w[i]<w[j]:v[i]>v[j];});
	for(int i=1;i<=n;i++)
		id[p[i]]=i,add(1,1,n,i,0);
	while(m--)
	{
		int op=read(),x=read();
		if(op==3)
		{
			k=17;
			printf("%lld\n",ask(1,1,n,x));
			continue;
		}
		int y=read();
		add(1,1,n,id[y],(op==1)?x:-x);
	}
}
posted @ 2022-05-31 22:45  C202044zxy  阅读(173)  评论(0编辑  收藏  举报