【NOI模拟赛】思门弄数(数论,链表)

题目背景

永远的神 OneInDark 出了一道没人做得出来的线性代数题,在用这道题击败 EI 大师后,跑到 crashed 门前求战。

但是没想到,crashed 还没完全推开门时,这道题就被祂干碎了,这超出了 OneInDark 对于祂秒切的预期。

“只给我找些线性代数题,这是在看不起我吗!” crashed 怒吼道。话音未落,二神就出现在了思门擂台上。

“看好了,我这次给你出道函数题!……”

OneInDark 端详了一秒,发现不对劲了,“嗯?这不是道生成函数!”

“你已经过了一秒了!哈哈!被创了吧!” crashed 大笑。

但是 OneInDark 立刻超光速把这题切了,使得最终完成时间变成了 − 1   m s -1\rm\,ms 1ms

题面

定义函数 F ( x ) = ∏ k = 0 ⌊ x − 1 ⌋ ( x − k 2 ) F(x)=\prod_{k=0}^{\lfloor\sqrt{x-1}\rfloor} (x-k^2) F(x)=k=0x1 (xk2) ,求在 [ l , r ] [l,r] [l,r] 中有多少个正整数 x x x 满足 F ( x ) F(x) F(x) m m m 的倍数。

输入一行 l r m ,输出一行答案。

限制: 1 ≤ l ≤ r ≤ 1 0 12 , 2 ≤ m ≤ 1 0 6 1\leq l\leq r\leq 10^{12},2\leq m\leq 10^6 1lr1012,2m106 ,1 s,128 mb 。

样例

1 1000000000000 1000000
599999999844

题解

当你完全用做数学题推式子的脑子想这道题,你就输了。

我们不妨修改成 F ( x ) = ∏ k = 0 ⌊ x − 1 ⌋ ( ( x − k 2 ) m o d    m ) F(x)=\prod_{k=0}^{\lfloor\sqrt{x-1}\rfloor} ((x-k^2)\mod m) F(x)=k=0x1 ((xk2)modm) ,于是不难发现, F ( x + m ) F(x+m) F(x+m) 一定是 F ( x ) F(x) F(x) 的倍数

这就意味着,取模 m m m 相等的一系列 x x x ,在 F ( x ) F(x) F(x) 是否是 m m m 的倍数这一问题上是单调的。

所以,我们可以求每一个同余类的最小的 x x x 满足 F ( x ) F(x) F(x) m m m 的倍数。又因为一个 k 2 k^2 k2 对于一个同余类的所有数贡献是相等的,所以我们只需要求出 k 2 k^2 k2 小于该 x x x 的最大的 k k k 就行了。

我们从小到大枚举 k k k ,并统计 k k k 的贡献。但是这样暴力是会超时的。

我们不妨将 m m m 质因数分解, m = ∏ p i w i m=\prod p_i^{w_i} m=piwi,对于每个 p i p_i pi 单独考虑枚举 k k k 算贡献,求出各个同余类包含 p i w i p_i^{w_i} piwi 的最小时间戳,最后再对每个同余类求时间戳的最大值。

这样每次枚举 k k k 的时候,从 k 2 m o d    m k^2\mod m k2modm 开始跳,每次往后跳 p i p_i pi 长度,用链表维护并 O ( 1 ) O(1) O(1) 跳过所有已经包含 p i w i p_i^{w_i} piwi 的位置,暴力乘贡献。由于每次计算都会使得一个同余类的 p i p_i pi 次数增加,最终到达 p i w i p_i^{w_i} piwi 停止,可以证明时间复杂度是 O ( m log ⁡ m ) O(m\log m) O(mlogm) 的。

总算法时间复杂度 O ( m log ⁡ m + r log ⁡ m ) O(m\log m+\sqrt{r}\log m) O(mlogm+r logm)

CODE

#include<map>
#include<set>
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<random>
#include<bitset>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<unordered_map>
#pragma GCC optimize(2)
using namespace std;
#define MAXN 1000005
#define LL long long
#define ULL unsigned long long
#define ENDL putchar('\n')
#define DB double
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
#define PR pair<int,int>
#define UIN unsigned int
int xchar() {
	static const int maxn = 1000000;
	static char b[maxn];
	static int pos = 0,len = 0;
	if(pos == len) pos = 0,len = fread(b,1,maxn,stdin);
	if(pos == len) return -1;
	return b[pos ++];
}
// #define getchar() xchar()
inline LL read() {
	LL f = 1,x = 0;int s = getchar();
	while(s < '0' || s > '9') {if(s<0)return -1;if(s=='-')f=-f;s = getchar();}
	while(s >= '0' && s <= '9') {x = (x<<1) + (x<<3) + (s^48);s = getchar();}
	return f*x;
}
void putpos(LL x) {if(!x)return ;putpos(x/10);putchar((x%10)^48);}
inline void putnum(LL x) {
	if(!x) {putchar('0');return ;}
	if(x<0) putchar('-'),x = -x;
	return putpos(x);
}
inline void AIput(LL x,int c) {putnum(x);putchar(c);}

int MOD = 1;
int n,m,s,o,k;
int dp[MAXN],f[MAXN];
LL c[MAXN];
PR fct[MAXN];
int hd[MAXN],nx[MAXN];
int main() {
	LL l = read(),r = read();MOD = read();
	int nn = MOD;
	for(int i = 2;i*i <= nn;i ++) {
		if(nn % i == 0) {
			int ct = 0;
			while(nn % i == 0) nn /= i,ct ++;
			fct[++ m] = {i,ct};
		}
	}
	if(nn > 1) fct[++ m] = {nn,1};
	for(int i = 1;i <= m;i ++) {
		int x = fct[i].FI,w = fct[i].SE,xx = 1;
		for(int j = 0;j < w;j ++) xx *= x;
		for(int j = 0;j < x;j ++) hd[j] = -1;
		for(int j = 0;j < MOD;j ++) {
			f[j] = 1000001,c[j] = 1;
			nx[j] = hd[j%x]; hd[j%x] = j;
		}
		for(int j = 0;j*1ll*j < r;j ++) {
			int ad = j*1ll*j%x,po = j*1ll*j%MOD;
			vector<int> emp;
			int tl = hd[ad]; hd[ad] = -1;
			for(int y = tl;y>=0;y = tl) {
				tl = nx[y];
				int le = (y+MOD-po)%MOD;
				(c[y] *= le) %= MOD;
				if(c[y] % xx == 0) f[y] = j;
				else nx[y] = hd[ad],hd[ad] = y;
			}
		}
		for(int j = 0;j < MOD;j ++) {
			dp[j] = max(dp[j],f[j]);
		}
	}
	LL ans = 0;
	for(int i = 0;i < MOD;i ++) {
		LL st = dp[i]*1ll*dp[i]+1;
		st = st + (MOD-st%MOD+i)%MOD;
		if(st <= r) ans += (r-st)/MOD+1;
		if(st < l) ans -= (l-1-st)/MOD+1;
	}
	AIput(ans,'\n');
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值