2021杭电多校(MINIEYE)第十场补题

1003 Pty loves lines

解题思路

最后一段连续的可行答案其实很长,记录一下对于每个 i i i来说最后一段连续的起始位置然后暴力转移.

代码

#include<bits/stdc++.h>
#define ll long long
#define MAXN 1001001
#define MOD 998244353
#define rep(i,s,t) for(int i=(s),i##end=(t);i<=i##end;++i)
#define dwn(i,s,t) for(int i=(s),i##end=(t);i>=i##end;--i)
#define ren for(int i=fst[x];i;i=nxt[i])
#define pb push_back
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int vis[MAXN],las[MAXN];
vector<int> ans[750];
int main()
{
	ans[1].pb(0);
	rep(i,2,700)
	{
		ans[i].pb(0);las[i]=las[i-1]+i-1;
		rep(j,1,i-1) for(auto x:ans[j]) if(!vis[x+j*(i-j)]&&(x+j*(i-j)<=las[i]))
			{ans[i].pb(x+j*(i-j));vis[x+j*(i-j)]=1;}
		sort(ans[i].begin(),ans[i].end());
		dwn(j,ans[i].size()-1,0)
			if(j==ans[i].size()-1||ans[i][j]+1==ans[i][j+1]) las[i]=ans[i][j];
			else break;
		for(auto x:ans[i]) vis[x]=0;
	}
	rep(T,1,read())
	{
		int n=read();
		for(auto x:ans[n]) if(x>las[n]) break;else printf("%d ",x);
		rep(i,las[n]+1,n*(n-1)/2) printf("%d%c",i,i==n*(n-1)/2?'\n':' ');
	}
}

1004 Pty hates prime numbers

解题思路

转载自此处
考虑最经典的容斥

枚举每个数选还是不选,那么 a n s + = ( n / S ) × ( − 1 ) t h e n u m b e r o f s e l e c t e d n u m b e r ans+=(n/S)×(−1)^{the number of selected number} ans+=(n/S)×(1)thenumberofselectednumber

即不断地加上选奇数个,减去选偶数个

复杂度为 O ( 2 k ) O(2^k) O(2k)

由于t比较大,这样显然过不了

考虑当k=8时,其乘积 S = 2 ∗ 3 ∗ 5 ∗ 7 ∗ 11 ∗ 13 ∗ 17 ∗ 19 = 9699690 S= 2∗3∗5∗7∗11∗13∗17∗19 = 9699690 S=235711131719=9699690

那么 a n s ( n ) = a n s ( 9699690 ) × ⌊ n 9699690 ⌋ + a n s ( n    m o d    9699690 ) ans(n)=ans(9699690)×⌊\frac n{9699690}⌋+ans(n\,\,mod \,\,9699690) ans(n)=ans(9699690)×9699690n+ans(nmod9699690)

预处理 1 − 9699690 1-9699690 19699690即可 O ( 1 ) O(1) O(1)查询 n 1 个 + n 2 个 ⋯ − n 7 个 + n 8 个 \frac {n} {1个}+\frac {n} {2个}⋯−\frac {n} {7个}+\frac {n} {8个} 1n+2n7n+8n

对于 k > 8 k>8 k>8的情况,可以 O ( 2 k − 8 ) O(2k−8) O(2k8)算出第 9 9 9到第 k k k个质数的情况,考虑前后相交的情况

显然有 n i ∗ j = n i j \frac n{i*j}=\frac{\frac n i}{j} ijn=jin

假设我们枚举后面一段的乘积为 r e s res res

与前面关联后我们需要求 − n 1 个 ∗ r e s + n 2 个 ∗ r e s ⋯ − n 7 个 ∗ r e s + n 8 个 ∗ r e s -\frac {n} {1个*res}+\frac {n} {2个*res}⋯−\frac {n} {7个*res}+\frac {n} {8个*res} 1resn+2resn7resn+8resn,再根据组合后的奇偶判断符号

r e s res res放到分子上,已知我们可以 O ( 1 ) O(1) O(1)去求 a n s ( n r e s ) = n r e s − n 1 个 ∗ r e s + n 2 个 ∗ r e s ⋯ − n 7 个 ∗ r e s + n 8 个 ∗ r e s ans(\frac n{res})=\frac n {res}-\frac {n} {1个*res}+\frac {n} {2个*res}⋯−\frac {n} {7个*res}+\frac {n} {8个*res} ans(resn)=resn1resn+2resn7resn+8resn

即可以在枚举后半部分时, O ( 1 ) O(1) O(1)处理前后关系

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=9699690;
int a[1<<17|1],ans1=0,prime[100],bj[110],cnt;
long long c[2*maxn+50];
struct tnode{
	int l,r;
}e[1<<17|1];
bool cmp(tnode l,tnode r)
{
	return l.l<r.l;
}
long long b[1<<17|1];
void get_prime(int x)
{
	for(int i=2;i<=x;i++)
	{
		if(!bj[i])prime[++cnt]=i;
		for(int j=1;j<=cnt&&prime[j]*i<=x;j++)
		{
			bj[i*prime[j]]=1;
			if(i%prime[j]==0)break;
		}
	}
}
int cal(int x)
{
	int ans=0;
	while(x)
	{
		ans++;
		x-=(x)&(-x);
	}
	return ans;
}
long long get_sum(long long n)
{
	return c[9699690]*(n/9699690)+c[n%9699690];
}
int main()
{
	get_prime(100);
	int t;
	scanf("%d",&t);
	for(int i=1;i<=8;i++)
	{
		a[1<<(i-1)]=prime[i];
	}
	for(int i=1;i<(1<<8);i++)
	{
		for(int j=0;j<8;j++)
		{
			if((i&(1<<j))==0)
			{
				a[i|(1<<j)]=a[i]*prime[j+1];
			}
		}
	}
	for(int i=1;i<=maxn;i++)
	c[i]=1;
	for(int i=1;i<(1<<8);i++)
	{
		if(cal(i)&1)
		{
			for(int j=1;a[i]*j<=maxn;j++)
			{
				c[a[i]*j]-=j;
				c[a[i]*(j+1)]+=j;
			}
		}
		else
		{
			for(int j=1;a[i]*j<=maxn;j++)
			{
				c[a[i]*j]+=j;
				c[a[i]*(j+1)]-=j;
			}
		}
	}
	for(int i=1;i<=maxn;i++)
	c[i]+=c[i-1];
	for(int i=1;i<=8;i++)
	{
		b[1<<(i-1)]=prime[i+8];
	}
	for(int i=1;i<(1<<8);i++)
	{
		for(int j=0;j<8;j++)
		{
			if((i&(1<<j))==0)
			{
				b[i|(1<<j)]=b[i]*prime[j+9];
			}
		}
	}
	while(t--)
	{
		int k;long long n;
		scanf("%lld%d",&n,&k);
		if(k<=8)
		{
			long long ans=n;
			for(int i=1;i<(1<<k);i++)
			{
				if(cal(i)&1)ans-=n/a[i];
				else ans+=n/a[i];
			}
			printf("%lld\n",ans);
		}
		else
		{
			k-=8;
			long long ans=n;
			ans=ans+(get_sum(n)-n);
			for(int i=1;i<(1<<k);i++)
			{
				if(cal(i)&1)
				{
					ans-=n/b[i];
					ans-=(get_sum(n/b[i])-n/b[i]);
				}
				else 
				{
					ans+=n/b[i];
					ans+=(get_sum(n/b[i])-n/b[i]);
				}
			}
			printf("%lld\n",ans);
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值