[SNOI2019]数论

前几天我们NOIP模拟赛的最后一题,然后一不小心挂了 $ 10 $ 分,痛失ak。

题目链接:luoguP5330

题目大意:给定两个数 $ P,Q $ 和两个集合 $ A,B $ 求 $ \sum\limits_{i=0}^{T-1}[(i\bmod P\in A)\wedge(i\bmod Q\in B)] $ 。

首先我们来看一个显而易见的规律:

假如说 $ P=4,Q=6 $。

$ 0\bmod P=0,0\bmod Q=0 $

$ 1\bmod P=1,1\bmod Q=1 $

$ \dots $

$ 11\bmod P=3,11\bmod Q=5 $

$ 12\bmod P=0,12\bmod Q=0 $

$ 13\bmod P=1,13\bmod Q=1 $

我们会发现,数字对于 $ P,Q $ 取模的值构成了“循环”,并且所有这些循环,对答案的贡献是相同的。

这些循环的答案非常容易计算,并且我们还可以知道一个循环的大小为 $ \dfrac{PQ}{\gcd(P,Q)} $ 。

这就是当 $ T $ 为 $ P,Q $ 公倍数时的解法。

配合暴力,我们可以拿到 $ 40 $ 分。

现在我们可以快速处理一个完整的循环,但是如果最后数字的个数不足以构成循环,我们就只能暴力。

显然,这样时间复杂度是 $ O(PQ) $ 的。

我们再来找规律,把数字 $ \bmod P,Q $ 的值做成表格。

$ P=4,Q=6: $

%P\%Q的值 0 1 2 3 4 5
0 0,12   8,20   4,16  
1   1,13   9,21   5,17
2 6,18   2,14   10,22  
3   7,19   3,15   11,23

$ P=5,Q=7: $

%P\%Q的值 0 1 2 3 4 5 6
0 0,35 15,50 30,65 10,45 25,60 5,40 20,55
1 21,56 1,36 16,51 31,66 11,46 26,61 6,41
2 7,42 22,57 2,37 17,52 32,67 12,47 27,62
3 28,63 8,43 23,58 3,38 18,53 33,68 13,48
4 14,49 29,64 9,44 24,59 4,39 19,54 34,69

发现什么了吗?

对于第一个表格,$ \bmod P=0 $ 的数字分别是 $ 0,4,8,12,16,20 $ ,这些数字 $ \bmod Q $ 的值分别是 $ 0,4,2,0,4,2 $ 。

$ \bmod P=1 $ 的数字分别是 $ 1,5,9,13,17,21 $ ,这些数字 $ \bmod Q $ 的值分别是 $ 1,5,3,1,5,3 $ 。

有没有发现什么?

再看一眼第二个表格。

$ \bmod P=0 $ 的数字分别是 $ 0,5,10,15,20,25,30,35,40,45,50,55,60,65 $ ,$ \bmod Q $ 的值分别是 $ 0,5,3,1,6,4,2,0,5,3,1,6,4,2 $ 。

$ \bmod P=1 $ 的数字分别是 $ 1,6,11,16,21,26,31,36,41,46,51,56,61,66 $ ,$ \bmod Q $ 的值分别是 $ 1,6,4,2,0,5,3,1,6,4,2,0,5,3 $ 。

发现了什么呢?

假如说上一个出现的数字 $ \bmod Q $ 的值是 $ x $ ,那么下一个值就是 $ (x+P)\bmod Q $ 。

好,现在让我们把这个序列写出来,但是这个序列中可能不包含 $ 0\sim Q-1 $ 中的所有数,所以我们把若干个这样的序列“拼”在一起。

举个例子,对于第二个表格,对应的序列就是 $ 0,5,3,1,6,4,2 $ ,对于第一个表格,对应的序列就是 $ 0,4,2,1,5,3 $ 。

然后呢?不难发现,对于某一个 $ \bmod P $ 的值,他对答案的贡献在这个序列中是完整的一段。

然后前缀和就可以了。

前缀和维护的是 $ 0~Q-1 $ 的这个排列中,前 $ i $ 个数字里面,有多少个属于集合 $ B $。

所以可以直接枚举 $ A $ 中的元素,计算它的贡献。

我考场上傻了,写了个线段树。

时间复杂度 $ O(P+Q) $ 。

我写的是 $ O(Q+P\log Q) $ 。

代码:

// DABC ABCD ABCA DBAA
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
#include<map>
using namespace std;
using pi=pair<int,int>;
using ll=long long;
using pl=pair<ll,ll>;
using pli=pair<ll,int>;
template<typename A>
using vc=vector<A>;
using vi=vector<int>;
inline int read()
{
	int s=0,w=1;char ch;
	while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
	while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
	return s*w;
}
inline ll lread()
{
	ll s=0,w=1;char ch;
	while((ch=getchar())>'9'||ch<'0') if(ch=='-') w=-1;
	while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
	return s*w;
}
int gcd(int a,int b)
{
	int r=a%b;
	while(r)
	{
		a=b;
		b=r;
		r=a%b;
	}
	return b;
}
bool pp[1000001];
bool qq[1000001];
int t1[4000001];
int val[1000001];
int wh[1000001];
int visa[1000001];
int visb[1000001];
int a[1000001];
int b[1000001];
int n,m,p,q;
int tot;
ll ans;
ll t;
void add(int p,int pl,int pr,int x)
{
	t1[p]++;
	if(pl==pr) return ;
	int mid=(pl+pr)>>1;
	if(x<=mid) add(p*2,pl,mid,x);
	else add(p*2|1,mid+1,pr,x);
}
int get(int p,int pl,int pr,int l,int r)
{
	if(l<=pl&&pr<=r) return t1[p];
	int mid=(pl+pr)>>1,ans=0;
	if(l<=mid) ans+=get(p*2,pl,mid,l,r);
	if(mid<r) ans+=get(p*2|1,mid+1,pr,l,r);
	return ans;
}
int main()
{
	p=read(),q=read(),n=read(),m=read(),t=lread();
	int num=gcd(p,q);ll round=(ll)p*q/num;
	for(int i=1;i<=n;i++) a[i]=read(),visa[a[i]%num]++,pp[a[i]]=1;
	for(int i=1;i<=m;i++) b[i]=read(),visb[b[i]%num]++,qq[b[i]]=1;
	for(int i=0;i<num;i++) ans+=(ll)visa[i]*visb[i]*(t/round);
	ll rest=t-(t/round)*round;
	for(int i=0;i<num;i++)
	{
		for(int j=i;;)
		{
			val[++tot]=j;
			wh[j]=tot;
			j=(j+p)%q;
			if(j==i) break;
		}
	}
	// for(int i=1;i<=q;i++) printf("%d ",val[i]);
	// printf("\n");
	for(int i=1;i<=m;i++) add(1,1,q,wh[b[i]]);
	for(int i=1;i<=n;i++)
	{
		int v=a[i];//第v行
		if(rest<i) continue;
		int step=rest/p;
		if(v<rest%p) step++;
		if(!step) continue;
		int l=wh[v%q],r=wh[v%q]+step-1;
		// printf("%d : %d %d ",a[i],l,r);
		l--,r--;
		// ll mem=ans;
		if(l/(q/num)==r/(q/num)) ans+=get(1,1,q,l+1,r+1);
		else
		{
			int l2=l/(q/num)*(q/num);
			int r2=l2+q/num-1;
			ans+=get(1,1,q,l2+1,r-(q/num)+1);
			ans+=get(1,1,q,l+1,r2+1);
			// printf("%d %d %d %d ",l2+1,r-(q/num)+1,l,r2);
		}
		// printf("%lld\n",ans-mem);
	}
	printf("%lld\n",ans);
	return 0;
}

感谢观看!

posted @ 2022-07-29 14:26  Wuyanru  阅读(67)  评论(0编辑  收藏  举报