[省选集训2022] 模拟赛15

Lost

题目描述

定义布尔函数 \(f(x)\) 表示 \(x\) 是否为完全平方数,给定 \(n,m\),求:

\[\sum_{i=1}^n\sum_{j=1}^m f(ij) \]

\(n,m\leq 10^{12}\)

解法

我们向深入考察 \(f(ij)\) 的性质,考虑 \(ij\) 为完全平方数的充要条件是,存在唯一的 \(d\) 满足 \(i=dx^2,j=dy^2\),考虑 \(d\) 是一个只包含一次质因子的数,所以可以枚举 \(d\)

\[\sum_{d=1}^{n}\sqrt{\frac{n}{d}}\cdot\sqrt{\frac{m}{d}}\cdot \mu(d)^2 \]

考虑对 \(u(d)^2\) 进行变换,有等式 \(\sum_{i^2|d}\mu(i)=\mu(d)^2\),证明可以考虑如果 \(d\) 是完全平方数,那么 \(i\) 可以自由选择包不包含某个质数 \(p\),包含和不包含的 \(\mu\) 之和是相反数,所以抵消。而否则 \(i\) 只能取 \(1\),就和 \(\mu(d)^2\) 相等了。我们带入这个等式:

\[\sum_{d=1}^{n}\sqrt{\frac{n}{d}}\cdot\sqrt{\frac{m}{d}}\cdot \sum_{i^2|d}\mu(i) \]

按照套路可以枚举 \(i\) 来化简这个式子:

\[\sum_{i=1}^{\sqrt n}\mu(i)\sum_{d}\sqrt{\frac{n/i^2}{d}}\cdot\sqrt {\frac{m/i^2}{d}} \]

注意根号的时候要强转 \(\tt ll\) 再做乘法才是符合组合意义的,外层可以直接枚举,内层整除分块,注意我们只对 \(\mu(i)\) 有值的位做整除分块,那么跑得就会非常快,其实是我算不来复杂度

补充:直接对 \(d\) 不包含平方因子这个条件做莫比乌斯反演,也可以得到最后的式子。

#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
const int M = 1000005;
#define int 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,cnt,ans,vis[M],p[M],mu[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];
		}
	}
}
int work(int n,int m)
{
	int res=0;
	for(int l=1,r=1;l<=n;l=r+1)
	{
		r=min(n/(n/l),m/(m/l));
		res+=(r-l+1)*(int)sqrt(n/l)*(int)sqrt(m/l);
	}
	return res;
}
signed main()
{
	freopen("lost.in","r",stdin);
	freopen("lost.out","w",stdout);
	n=read();m=read();init(1e6);
	if(n>m) swap(n,m);
	for(int i=1,t=sqrt(n);i<=t;i++) if(mu[i])
		ans+=mu[i]*work(n/i/i,m/i/i);
	printf("%lld\n",ans);
}

dignity

题目描述

给定 \(n\) 个数 \(a_i\),设 \(f(T)\) 表示,满足 \(\sum_{i=1}^n a_ic_i=T\) 的数组 \(c_i\) 的个数。

\(q\) 次询问,每次询问 \(h_i\),求出最小的 \(T\) 使得 \(f(T)\geq h_i\)

\(n\leq 5,a_i\leq10,q\leq 100,h_i\leq 10^{15}\)

解法

我竟然没有想到矩阵加速!考虑求出 \(f(T)\) 其实就是一个完全背包问题,我们设 \(dp(i,j)\) 表示使用 \(i\) 个数,其总和为 \(j\) 的方案数,那么我们对第二维矩阵加速即可,只需要保留 \(10\) 范围以内的 \(j\),所以矩阵大小是 \(50\times 50\) 的。

询问可以考虑倍增,最大方案数是具有单调性的,预处理次幂就可以做到 \(O(50^3\cdot \log h+q\cdot 50^2\cdot\log h)\)

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 55;
#define int long long
#define zz __int128
const zz 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,a[M],id[M][M];
struct mat
{
	int n,m,a[M][M];
	mat(int A=0,int B=0) {n=A;m=B;memset(a,0,sizeof a);}
	mat operator * (const mat &b) const
	{
		mat r=mat(n,b.m);
		for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
			for(int k=1;k<=b.m;k++)
			{
				zz t=min(inf,(zz)a[i][j]*b.a[j][k]);
				r.a[i][k]=min(inf,r.a[i][k]+t);
			}
		return r;
	}
}A[62],B;
signed main()
{
	freopen("dignity.in","r",stdin);
	freopen("dignity.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++)
		for(int j=0;j<10;j++)
			id[i][j]=++k;
	A[0]=mat(k,k);B=mat(1,k);
	for(int i=1;i<=n;i++)
		a[i]=read(),B.a[1][id[i][9]]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<9;j++)
			A[0].a[id[i][j+1]][id[i][j]]=1;
		if(i>1) for(int j=1;j<=k;j++)
			A[0].a[j][id[i][9]]=A[0].a[j][id[i-1][9]];
		A[0].a[id[i][10-a[i]]][id[i][9]]=1;
	}
	for(int i=1;i<=60;i++) A[i]=A[i-1]*A[i-1];
	m=read();
	while(m--)
	{
		int x=read(),lim=100*x,ans=0;mat F=B;
		for(int i=60;i>=0;i--) if(ans+(1ll<<i)<lim)
		{
			mat tmp=F*A[i];int mx=0;
			for(int j=0;j<10;j++)
				mx=max(mx,tmp.a[1][id[n][j]]);
			if(mx<x) F=tmp,ans+=(1ll<<i);
		}
		if(x>1) ans++,F=F*A[0];int mx=0;
		for(int j=0;j<10;j++)
			mx=max(mx,F.a[1][id[n][j]]);
		if(mx>=x) printf("%lld\n",ans);
		else puts("What a pity!");
	}
}

CF643G Choosing Ads

题目描述

点此看题

解法

话说我当时看这题是紫的就把他跳过了,可能我刷再多题也做不到原题吧

题目的提示已经很明显了,我们只需要找出一些满足必要条件的数即可。

考虑 \(p=51\) 的时候(也就是求区间的绝对众数),有一种方法是每次删去两个不同的数,那么如果区间存在绝对总数一定会存留到最后。

这种做法是可以推广的,设 \(k=\lfloor\frac{100}{p}\rfloor\),那么我们每次删除 \(k+1\) 个不同的数,不难证明可能成为答案的数一定会存留到最后。

这东西可以用线段树暴力维护,时间复杂度 \(O(n\log n\cdot k^2)\)

#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 150005;
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,p,k,fl[M<<2];
struct node
{
	int a[10],b[10];
	void clear()
	{
		for(int i=0;i<p;i++) a[i]=b[i]=0;
	}
	node operator + (const node &o) const
	{
		node r=(*this);
		for(int i=0;i<p;i++)//add o[i]
		{
			int fl=0;
			for(int j=0;j<p;j++) if(o.a[i]==r.a[j])
				{fl=1;r.b[j]+=o.b[i];break;}
			if(fl) continue;
			r.b[p]=o.b[i];
			int x=p,mn=0;
			for(int j=0;j<p;j++) if(r.b[j]<r.b[x]) x=j;
			mn=r.b[x];
			for(int j=0;j<p;j++) r.b[j]-=mn;
			if(x<p) r.a[x]=o.a[i],r.b[x]=o.b[i]-mn;
		}
		return r;
	}
	void print()
	{
		printf("%d",p);
		for(int i=0;i<p;i++)
			printf(" %d",a[i]);
		puts("");
	}
}t[M<<2],ans;
void cov(int i,int len,int c)
{
	fl[i]=c;t[i].clear();
	t[i].a[0]=c;t[i].b[0]=len;
}
void down(int i,int l,int r)
{
	if(!fl[i]) return ;
	int mid=(l+r)>>1;
	cov(i<<1,mid-l+1,fl[i]);
	cov(i<<1|1,r-mid,fl[i]);fl[i]=0;
}
void build(int i,int l,int r)
{
	if(l==r)
	{
		t[i].a[0]=read();t[i].b[0]=1;
		return ;
	}
	int mid=(l+r)>>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
	t[i]=t[i<<1]+t[i<<1|1];
}
void add(int i,int l,int r,int L,int R,int c)
{
	if(L>r || l>R) return ;
	if(L<=l && r<=R) {cov(i,r-l+1,c);return ;}
	int mid=(l+r)>>1;down(i,l,r);
	add(i<<1,l,mid,L,R,c);
	add(i<<1|1,mid+1,r,L,R,c);
	t[i]=t[i<<1]+t[i<<1|1];
}
void ask(int i,int l,int r,int L,int R)
{
	if(L>r || l>R) return ;
	if(L<=l && r<=R) {ans=ans+t[i];return ;}
	int mid=(l+r)>>1;down(i,l,r);
	ask(i<<1,l,mid,L,R);
	ask(i<<1|1,mid+1,r,L,R);
}
signed main()
{
	n=read();m=read();k=read();p=100/k;
	build(1,1,n);
	while(m--)
	{
		int op=read(),l=read(),r=read();
		if(op==1) add(1,1,n,l,r,read());
		else
		{
			ans.clear();
			ask(1,1,n,l,r);
			ans.print();
		}
	}
}
posted @ 2022-03-20 15:17  C202044zxy  阅读(178)  评论(4编辑  收藏  举报