NOIP模拟60
T1:
连续考到CRT,基本套路为首先观察到n很大,然而直接给出了n的唯一分解
并且唯一分解中每一项均为一次,那么显然的CRT(非拓展)
考虑如何合并,首先若n | x^m - x,那么对于n唯一分解中的质数p,一定满足
p | x^m - x,通过简单的转化,我们可以将问题转化为x^m = x (mod n),那么根据
上述分析,同余方程可以转化为(x % p)^m = x % p (mod p),以此建立由x到x % p
的单一映射。
考虑一个很关键的问题为问题只需要求满足条件的x的个数而并不需要求x具体值
通过CRT的知识可以知道,在模n意义下,同余方程组在[1,n]仅存在一组解,也就是
贡献为一,那么考虑有多少种满足条件的x实际上可以转化为有多少种同余方程组。
一个常识为对于x^m = x (mod p)这种高次同余方程CRT显然不适用,那么考虑
要应用CRT计数显然就需要将高次同余方程转化为线性同余方程,至此,问题只求
x个数与之前的映射就起到了作用。
我们发现由于x与x % p的映射关系是一一对应的(正射反射),那么对于高次
同余方程x^m = x (mod p)在计数意义下就可以转化为x = x % p (mod p),以此将
所有高次方程转化为线性方程后就可以根据计数原理直接计算方程组数来计数
具体过程为,对于每一质数,求解在[1,prime]范围内满足条件的x的个数,最终
将所有num相乘即可,发现对于极限数据并不能通过,然而时间复杂度相差不大,
这里有三种优化方法:
1:sitiy:
考虑一个很明显的条件为在prime下计算x时,x的取值范围为[1.prime],而prime
又为质数,因此直接应用费马定理进行化简,将x^m转化为x^(m % (prime - 1))
(费马定理:若prime为质数且(a,prime) = 1,则a ^ (prime - 1) = 1 (mod prime)实际上
就是欧拉定理),于是常数约减小2~3倍
代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define I int 4 #define C char 5 #define B bool 6 #define V void 7 #define D double 8 #define LL long long 9 #define UI unsigned int 10 #define UL unsigned long long 11 #define P pair<I,I> 12 #define MP make_pair 13 #define fir first 14 #define sec second 15 #define lowbit(x) (x & -x) 16 const I mod = 998244353; 17 inline I read () { 18 I x(0),y(1); C z(getchar()); 19 while (!isdigit(z)) { if (z == '-') y = -1; z = getchar(); } 20 while ( isdigit(z)) x = x * 10 + (z ^ 48), z = getchar(); 21 return x * y; 22 } 23 inline V Max (I &a,I b) { a = a > b ? a : b; } 24 inline V Min (I &a,I b) { a = a < b ? a : b; } 25 inline I max (I a,I b) { return a > b ? a : b; } 26 inline I min (I a,I b) { return a < b ? a : b; } 27 inline V swap (I &a,I &b) { a ^= b, b ^= a, a ^= b; } 28 inline I abs (I a) { return a >= 0 ? a : -a; } 29 inline P operator + (const P &a,const P &b) { 30 return MP (a.fir + b.fir,a.sec + b.sec); 31 } 32 inline P operator - (const P &a,const P &b) { 33 return MP (a.fir - b.fir,a.sec - b.sec); 34 } 35 inline I fp (I a,I b,I c) { 36 I ans(1); 37 for (; b ;b >>= 1, a = a * a % c) 38 if (b & 1) ans = ans * a % c; 39 return ans; 40 } 41 signed main () { 42 I idx (read()), T (read()); 43 while (T -- ) { 44 I ans (1),c (read()),m (read()); 45 for (I i(1);i <= c; ++ i) { 46 I prime (read()), num (0), tmp (m % (prime - 1)); 47 for (I j(1);j < prime; ++ j) 48 if (fp (j,tmp,prime) == j % prime) 49 if (++ num > mod) num -= mod; 50 ans = 1ll * ans * (num + 1) % mod; 51 } 52 printf ("%d\n",ans); 53 } 54 }
2:积性筛:
仍然考虑优化x^m计算过程,考虑通过积性筛预处理x^m,其中x为[1,prime]
将log消去
3:zero4338:
利用原根化简,gcd计数,时间复杂度log,爆踩标算
Update:大帝的问题1:在正射过程中,显然x + p与x在模p意义下会映射为同一数,
如何保证计数过程中不重不漏。
考虑计数原理,CRT在模n意义下在[1,n]区间内存在唯一解,而一个合法x的统计
实际上是由一组同余方程组计算得出,那么当x与x + p在模q意义下显然不同,即x与
x + p所属同余方程组不同,保证计数正确性,考虑x与x + p在模所有质数下完全相同的
情况,那么x显然等于n,此时由于x属于[1,n],那么x + p显然不合法,因此同样成立。
大帝的问题2:考虑一种简单情况,存在两个x合法,n唯一分解出四个质数,
那么两个x在模p意义下分别产生两种映射,根据计算方式,答案为2^4,显然不正确
考虑映射的本质是舍弃一部分原始信息,保留能产生一一对应关系的信息,以此
将问题转化,而再考虑原值显然是不正确的,考虑本题的计数原理为CRT在模n意义下
在[1,n]存在唯一解,于是我们利用计算有多少组同余方程来计算有多少合法的x,而
不是根据有多少映射计算,因为当x不合法时也会产生映射,而当且仅当x合法时会被
计入对应的质数p的方程中。
实际上是先通过p计算满足条件的x % p,在一一反射计数
这就是计数问题中经典方法——映射
T2:
神仙倍增DP,首先可以很容易DP出a相同的情况,于是根据a分层做多次倍增DP
,现在问题在于如何合并答案,利用背包DP进行合并即可
设f[i][j]表示前i层选j个物品的方案数,那么f[i][j] = sigma f[i - 1][k] * h[i][j - k] * C(j,k)
其中h[i][j]表示在第i层选择j个物品的方案数,那么现在问题则为求解h[i][j],倍增DP利用
二进制思想简化时间复杂度,因此在取答案时也需要二进制合并,考虑如何做
比较显然的方程为h[i][j] = sigma h[i][j - k] * g[i][x][k] * C(j,k),x为二进制分解
然而仔细分析发现存在重复计算,思考常规背包DP利用倒叙枚举的方式解决重复计算
问题,然而我们发现似乎并不能应用于此题,仔细分析发现原因在于本DP并没有严格的
阶段划分,因此利用倒叙划分出阶段的方法并不适用,那么直观的想法为能否人为划分出
阶段,答案时可以的,我们发现可以利用二进制位进行阶段划分,即增加以为状态记录
当前第几次二进制位更新,在每次更新时,只利用上一次更新的答案进行更新,避免重复
计算。实际上是通过增加一维,DP数组并不记录历史的所有状态,而只记录某一二进制位
下的答案,因此能够避免重复计算。
所以当背包DP存在重复计算有没有严格的阶段时可以人为设计阶段进行划分