P5285 [十二省联考2019]骗分过样例
作者:互联网
根据数据猜测题目内容并完成输出,打表是不太可能的。
真正的传统题在这里(doge
那就一个一个分析,这其实是一道 数论大杂烩 + 乱搞精神 的题目。
前置芝士
这题的黑科技较多,请耐心食用。
快速乘
区别于 \(O(\log n)\) 的龟速乘。
一般两个 long long 级别的数相乘用的是龟速乘,大概长这样
LL Mul(LL a, LL b, LL p){
LL sum = 0;
for(; b; b >>= 1){
if(b & 1) sum = (sum + a) % p;
a = (a + a) % p;
}
return sum;
}
但是它让你的代码无端多了个 \(O(\log n)\),看上去很不爽,于是有了黑科技 \(O(1)\) 快速乘。
LL Mul(LL a, LL b, LL p){
a %= p, b %= p;
LL c = (long double) a * b / p;
c = a * b - c * p;
if(c < 0) return c + p;
if(c >= p) return c - p;
return c;
}
本质上利用了 \(a\ {\rm mod}\ b=a-b\times \lfloor \frac{a}{b}\rfloor\) 的原理。
Miller_Rabin
理论 \(O(n^{1/4})\) 的素数判定算法。
利用费马小定理(若 \(p\) 为素数,那么 \(a^{p-1}\equiv 1\pmod p\))和二次探测定理,有大概率不会判定错误。
具体了解自行百度,这里为了卡常,只用了前两个质数,但是正确性没有受到影响。
void Pre(){
memset(vis, false, sizeof(vis));
for(int i = 2; i <= N - 10; i ++){
if(!vis[i]) prm[++ tot] = i;
for(int j = 1; j <= tot && 1LL * prm[j] * i <= N; j ++){
vis[prm[j] * i] = true;
if(i % prm[j] == 0) break;
}
}
}
bool Miller_Rabin(LL x){
if(x < 3) return x == 2;
if(x <= N) return !vis[x];
for(int i = 1; i <= 30; i ++)
if(x % prm[i] == 0) return false;
LL p[3] = {0, 2, 3};
LL t = x - 1, k = 0;
while(!(t & 1)) k ++, t >>= 1;
for(int i = 1; i <= 2; i ++){
if(x == p[i]) return true;
LL a = Pow(p[i], t, x), nxt;
for(int j = 1; j <= k; j ++){
nxt = Mul(a, a, x);
if(nxt == 1 && a != 1 && a != x - 1) return false;
a = nxt;
}
if(a != 1) return false;
}
return true;
}
原根相关
对于质数 \(p\),若存在正整数 \(a\),满足 \(a^{0\sim p-1}\mod p\) 的值都不相同,称 \(a\) 为 \(p\) 的原根。
显然,原根的判定即为除了 \(a^{p-1}\equiv 1\pmod p\) 之外没有其它指数使之成立。
不难得到若存在 \(a^{x}\equiv 1\pmod p,x<p-1\),那么 \(x|p-1\)。
难道需要枚举 \(a\) 的所有约数吗,这样的时间复杂度过高。
但是不难发现,若 \(a^{x}\equiv 1\pmod p\) 那么 \(a^{x\times t}\equiv 1\pmod p\),所以只需要用 \(p-1/\) 它的每一个质因子来判断即可。
void Div(int x){
for(int i = 2; i * i <= x; i ++) if(x % i == 0){
p[++ cnt] = i;
while(x % i == 0) x /= i;
}
if(x) p[++ cnt] = x;
}
bool G(int x){
for(int i = 1; i <= cnt; i ++)
if(Pow(x, (P - 1) / p[i], P) == 1) return false;
return true;
}
单次判断时间复杂度为 \(O(\log^2 n)\)。
还有就是若 \(x\) 为原根,那么 \(y=x^a\) 为原根当且仅当 \((y,\varphi(p))=1\)。
于是我们可以枚举 \(\varphi(p)\) 的因子,然后枚举倍数,筛掉其它的非原根数,时间复杂度为 \(O(n\log \log n)\)。
这也证明了原根的个数为 \(\varphi(\varphi(p))\) 个。
1_998244353
肉眼观察输出得到 \(ans=19^x\),模数显然是标题中的 998244353
。
于是一个快速幂即可。
但是发现数据 \(3\) 的大小达到了 \(10^{40}\) 级别,那么利用欧拉定理,边输入边对 998344352
取模即可。
当然你也可以写高精,但是写高精是不可能的,这辈子都不可能的。
LL Mul(LL a, LL b, LL p){
a %= p, b %= p;
LL c = (long double) a * b / p;
c = a * b - c * p;
if(c < 0) return c + p;
if(c >= p) return c - p;
return c;
}
LL Get_Num(LL p){
string num; cin >> num;
int len = num.length();
LL sum = 0;
for(int i = 0; i < len; i ++)
sum = (Mul(sum, 10, p) + (num[i] - '0')) % p;
return sum;
}
LL Pow(LL a, LL b, LL p){
LL sum = 1;
for(; b; b >>= 1){
if(b & 1) sum = Mul(sum, a, p);
a = Mul(a, a, p);
}
return sum;
}
void Work1(LL MOD){
int n = read();
while(n --){
LL a = Get_Num(MOD - 1);
printf("%lld\n", Pow(19, a, MOD));
}
}
if(opt == "1_998244353") Work1(998244353);
1?
不知道模数了,但是可以暴力枚举 \(1\sim 10^7\) 中的每个数,然后根据前 \(15\) 个结果判断就差不多了。
判断的代码:
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long LL;
LL ans[] = {0, 1, 19, 361, 642666, 986870, 310269, 992342, 384794, 134675, 177504, 1002776, 649864, 664505, 128746, 56645};
string num[20];
LL Get_Num(string num, LL p){
int len = num.length();
LL sum = 0;
for(int i = 0; i < len; i ++)
sum = (sum * 10LL + num[i] - '0') % p;
return sum;
}
LL Mul(LL a, LL b, LL p){
LL sum = 0;
for(; b; b >>= 1){
if(b & 1) sum = (sum + a) % p;
a = (a + a) % p;
}
return sum;
}
LL Pow(LL a, LL b, LL p){
LL sum = 1;
for(; b; b >>= 1){
if(b & 1) sum = Mul(sum, a, p);
a = Mul(a, a, p);
}
return sum;
}
bool chck(LL MOD){
for(int i = 1; i <= 15; i ++)
if(Pow(19, Get_Num(num[i], MOD - 1), MOD) != ans[i]) return false;
return true;
}
int main(){
num[1] = "0";
num[2] = "1";
num[3] = "2";
num[4] = "627811703016764290815178977207148434322";
num[5] = "856773959631992699884816425292659199878";
num[6] = "9252516556604991340257983180339089701407";
num[7] = "489253720019916666717422858681089087632";
num[8] = "361492456259178260319627241577661012313";
num[9] = "6873285858347382093454667858252046752166";
num[10] = "3148173111755199960582784172788830450730";
num[11] = "503142180766309744162636115563208116498";
num[12] = "198994173692720779018490094767574486547";
num[13] = "9512507902619525009686627736024447779464";
num[14] = "7352719788670584612661569096966195045732";
num[15] = "5392078508855533698663328274209427052165";
for(LL MOD = 2; MOD <= 10000000; MOD ++)
if(chck(MOD)) printf("%lld\n", MOD);
return 0;
}
得到结果:1145141
。于是:
else if(opt == "1?") Work1(1145141);
1?+
不难发现模数变的很大,无法穷举判断了。
但是我们可以在数据中找到三元组:\((x,y,z)\) 使得 \(y-x=z\)。
那么有模数 \(p\) 是 \(ans_x\times ans_z - ans_y\) 的约数,然后就是枚举大数约数的问题了。
需要用到 pollard_rho 算法,或者有很多神奇方法(比如在 NOI Linux 上调用 factor 指令)都可以。
得到模数为 5211600617818708273
。于是:
else if(opt == "1?+") Work1(5211600617818708273);
1wa_998244353
一个好好的题目被搞 wa 掉了,原因很简单是因为整型溢出。因为溢出也不太好搞到规律,或许可以近似认为是随机数。
那么根据生日悖论,在很小的范围内就有很大概率可以找到循环节,代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
int MOD = 998244353;
map<int, int> vis;
int main(){
int x = 1;
for(int i = 1; ; i ++){
if(vis[x = x * 19 % MOD]){
printf("%d %d\n", vis[x], i - vis[x]);
return 0;
}
else vis[x] = i;
}
return 0;
}
结果为 55245 45699
。
于是直接上代码就是了:
void Work2(){
int n = read(), MOD = 998244353;
LL A = 55245, B = 45699;
ans[0] = 1;
for(int i = 1; i <= A + B; i ++)
ans[i] = ans[i - 1] * 19 % MOD;
while(n --){
LL x; scanf("%lld", &x);
printf("%d\n", ans[x < A ? x : (x - A) % B + A]);
}
}
else if(opt == "1wa_998244353") Work2();
2p
显然是判断一个区间内的素数,那么直接 Miller_Rabin 即可。
void Pre(){
memset(vis, false, sizeof(vis));
for(int i = 2; i <= N - 10; i ++){
if(!vis[i]) prm[++ tot] = i;
for(int j = 1; j <= tot && 1LL * prm[j] * i <= N; j ++){
vis[prm[j] * i] = true;
if(i % prm[j] == 0) break;
}
}
}
bool Miller_Rabin(LL x){
if(x < 3) return x == 2;
if(x <= N) return !vis[x];
for(int i = 1; i <= 30; i ++)
if(x % prm[i] == 0) return false;
LL p[3] = {0, 2, 3};
LL t = x - 1, k = 0;
while(!(t & 1)) k ++, t >>= 1;
for(int i = 1; i <= 2; i ++){
if(x == p[i]) return true;
LL a = Pow(p[i], t, x), nxt;
for(int j = 1; j <= k; j ++){
nxt = Mul(a, a, x);
if(nxt == 1 && a != 1 && a != x - 1) return false;
a = nxt;
}
if(a != 1) return false;
}
return true;
}
void Work3(){
int n = read();
while(n --){
LL l, r; scanf("%lld %lld", &l, &r);
for(LL i = l; i <= r; i ++)
if(Miller_Rabin(i)) putchar('p'); else putchar('.');
puts("");
}
}
2u
莫比乌斯函数。
分别达到了 \(10^6,10^{12},10^{18}\) 级别,当然区间长度都比较小。
第一种直接线性筛,第二种可以优先筛出 \([1,\sqrt R]\),然后用这个筛 \([L,R]\) 即可。
对于三,先还是用 \([1,10^7]\) 来筛一遍,然后剩余的大部分数字都降到了 \(10^{7}\) 级别之下,但难免意外。
那么就简单讨论一下,对于剩余数字 \(a\):
- \(a\) 为质数(用 Miller_Rabin 判断,或者根据 \(a\leq 10^{14}\) 量级直接得到 \(a\) 一定是一个大质数),\(\mu(i)=-\mu(i)\)。
- \(a\) 为完全平方数,\(\mu(i)=0\)。
- \(a\) 为两个大数的乘积,\(\mu(i)=\mu(i)\)。
于是就筛完了。
int u[N]; LL c[N];
void Work4(){
int n = read();
while(n --){
LL l, r; scanf("%lld %lld", &l, &r);
for(LL i = 1; i <= r - l + 1; i ++)
u[i] = 1, c[i] = i + l - 1;
for(LL i = 1; i <= tot; i ++){
LL x = prm[i];
for(LL p = x * ((l - 1) / x + 1) - l + 1; p <= r - l + 1; p += x){
if(c[p] % (x * x) == 0) u[p] = 0;
else u[p] = -u[p], c[p] /= x;
}
}
for(LL i = 1; i <= r - l + 1; i ++){
LL x = c[i];
if(u[i] && x > 1){
LL y = sqrt(x);
if(x == y * y) u[i] = 0;
else if(x <= 1e14 || Miller_Rabin(x)) u[i] = -u[i];
}
if(u[i]) putchar(u[i] == 1 ? '+' : '-');
else putchar('0');
}
puts("");
}
}
else if(opt == "2u") Work4();
2g
原根,利用前置芝士里的内容不难得到代码。
值得一提的是当模数比较大的时候,区间范围就比较小,而区间范围达到 \(10^6\) 的时候,模数又比较小。
这启发我们用两种不同的方法,区间范围较小的时候直接暴力枚举 + \(O(\log^2 n)\) 判断,否则利用因子倍数来预处理筛原根。
int cnt, P, p[N], pos[N << 1];
void Div(int x){
for(int i = 2; i * i <= x; i ++) if(x % i == 0){
p[++ cnt] = i;
while(x % i == 0) x /= i;
}
if(x) p[++ cnt] = x;
}
bool G(int x){
for(int i = 1; i <= cnt; i ++)
if(Pow(x, (P - 1) / p[i], P) == 1) return false;
return true;
}
void Work5(){
int n = read();
while(n --){
cnt = 0; int l, r;
l = read(), r = read();
if(l == 233333333) P = 1515343657; else P = read();
Div(P - 1);
if(r - l + 1 <= 1e6){
for(int i = l; i <= r; i ++)
if(G(i)) putchar('g'); else putchar('.');
}
else{
memset(vis, false, sizeof(vis));
for(int i = 1; i <= cnt; i ++)
for(int j = 1; j <= (P - 1) / p[i]; j ++)
vis[p[i] * j] = true;
int x = 2;
for(; !G(x); x ++);
for(int i = 1, num = x; i < P; num = 1LL * num * x % P, i ++)
pos[num] = i;
for(int i = l; i <= r; i ++)
if(vis[pos[i]]) putchar('.'); else putchar('g');
}
puts("");
}
}
else Work5();
代码总结
于是这道题就结束了,总体来讲题目形式还是很新颖的,而且很好的考验了选手的初等数论能力(和乱搞能力)。
个人有非常良好的做题体验感,感觉挺有趣的。
AC Code:
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
using namespace std;
typedef long long LL;
const int N = 10000010;
int tot, ans[200010], prm[N];
bool vis[N + 10];
int read(){
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
void Pre(){
memset(vis, false, sizeof(vis));
for(int i = 2; i <= N - 10; i ++){
if(!vis[i]) prm[++ tot] = i;
for(int j = 1; j <= tot && 1LL * prm[j] * i <= N; j ++){
vis[prm[j] * i] = true;
if(i % prm[j] == 0) break;
}
}
}
LL Mul(LL a, LL b, LL p){
a %= p, b %= p;
LL c = (long double) a * b / p;
c = a * b - c * p;
if(c < 0) return c + p;
if(c >= p) return c - p;
return c;
}
LL Get_Num(LL p){
string num; cin >> num;
int len = num.length();
LL sum = 0;
for(int i = 0; i < len; i ++)
sum = (Mul(sum, 10, p) + (num[i] - '0')) % p;
return sum;
}
LL Pow(LL a, LL b, LL p){
LL sum = 1;
for(; b; b >>= 1){
if(b & 1) sum = Mul(sum, a, p);
a = Mul(a, a, p);
}
return sum;
}
void Work1(LL MOD){
int n = read();
while(n --){
LL a = Get_Num(MOD - 1);
printf("%lld\n", Pow(19, a, MOD));
}
}
void Work2(){
int n = read(), MOD = 998244353;
LL A = 55245, B = 45699;
ans[0] = 1;
for(int i = 1; i <= A + B; i ++)
ans[i] = ans[i - 1] * 19 % MOD;
while(n --){
LL x; scanf("%lld", &x);
printf("%d\n", ans[x < A ? x : (x - A) % B + A]);
}
}
bool Miller_Rabin(LL x){
if(x < 3) return x == 2;
if(x <= N) return !vis[x];
for(int i = 1; i <= 30; i ++)
if(x % prm[i] == 0) return false;
LL p[3] = {0, 2, 3};
LL t = x - 1, k = 0;
while(!(t & 1)) k ++, t >>= 1;
for(int i = 1; i <= 2; i ++){
if(x == p[i]) return true;
LL a = Pow(p[i], t, x), nxt;
for(int j = 1; j <= k; j ++){
nxt = Mul(a, a, x);
if(nxt == 1 && a != 1 && a != x - 1) return false;
a = nxt;
}
if(a != 1) return false;
}
return true;
}
void Work3(){
int n = read();
while(n --){
LL l, r; scanf("%lld %lld", &l, &r);
for(LL i = l; i <= r; i ++)
if(Miller_Rabin(i)) putchar('p'); else putchar('.');
puts("");
}
}
int u[N]; LL c[N];
void Work4(){
int n = read();
while(n --){
LL l, r; scanf("%lld %lld", &l, &r);
for(LL i = 1; i <= r - l + 1; i ++)
u[i] = 1, c[i] = i + l - 1;
for(LL i = 1; i <= tot; i ++){
LL x = prm[i];
for(LL p = x * ((l - 1) / x + 1) - l + 1; p <= r - l + 1; p += x){
if(c[p] % (x * x) == 0) u[p] = 0;
else u[p] = -u[p], c[p] /= x;
}
}
for(LL i = 1; i <= r - l + 1; i ++){
LL x = c[i];
if(u[i] && x > 1){
LL y = sqrt(x);
if(x == y * y) u[i] = 0;
else if(x <= 1e14 || Miller_Rabin(x)) u[i] = -u[i];
}
if(u[i]) putchar(u[i] == 1 ? '+' : '-');
else putchar('0');
}
puts("");
}
}
int cnt, P, p[N], pos[N << 1];
void Div(int x){
for(int i = 2; i * i <= x; i ++) if(x % i == 0){
p[++ cnt] = i;
while(x % i == 0) x /= i;
}
if(x) p[++ cnt] = x;
}
bool G(int x){
for(int i = 1; i <= cnt; i ++)
if(Pow(x, (P - 1) / p[i], P) == 1) return false;
return true;
}
void Work5(){
int n = read();
while(n --){
cnt = 0; int l, r;
l = read(), r = read();
if(l == 233333333) P = 1515343657; else P = read();
Div(P - 1);
if(r - l + 1 <= 1e6){
for(int i = l; i <= r; i ++)
if(G(i)) putchar('g'); else putchar('.');
}
else{
memset(vis, false, sizeof(vis));
for(int i = 1; i <= cnt; i ++)
for(int j = 1; j <= (P - 1) / p[i]; j ++)
vis[p[i] * j] = true;
int x = 2;
for(; !G(x); x ++);
for(int i = 1, num = x; i < P; num = 1LL * num * x % P, i ++)
pos[num] = i;
for(int i = l; i <= r; i ++)
if(vis[pos[i]]) putchar('.'); else putchar('g');
}
puts("");
}
}
int main(){
Pre();
string opt; cin >> opt;
if(opt == "1_998244353") Work1(998244353);
else if(opt == "1?") Work1(1145141);
else if(opt == "1?+") Work1(5211600617818708273);
else if(opt == "1wa_998244353") Work2();
else if(opt == "2p") Work3();
else if(opt == "2u") Work4();
else Work5();
return 0;
}
标签:过样,骗分,return,int,LL,num,sum,include,联考 来源: https://www.cnblogs.com/lpf-666/p/14974572.html