题解:CF1109E Sasha and a Very Easy Test

· · 题解

upd 2024.9.11 修正一处笔误。

也是好起来了,晚自习跑机房集训了。

因为变量重名调了半个晚上,太菜了 qwq。

Solution

但不是质数,单点除法就不能用逆元,我们是没有办法处理带模除法的。 质数要求太过苛刻,我们可以用欧拉定理: $$x^{\varphi(x)}\equiv 1\pmod p,\quad \gcd(x,p)=1$$ 此时 $x$ 的逆元即为 $x^{\varphi(x)-1}$。 但是我们仍然需要除数和 $mod$ 互质,所以考虑如何把它变得互质。显然地,把不互质的部分去掉不就互质了吗? 这启发我们分开维护。先将 $mod$ 进行唯一分解,可以发现不同的质因子不会超过 $9$ 个。那么对于除法给出的每一个 $x$,我们可以单独处理 $x$ 中含有的 $mod$ 的因子并且把它们干掉,剩下的直接求逆元就好了。 具体地,用线段树维护区间和与区间乘标记。由于除法是单点除法,所以在线段树中每次都会下到最底层。 因此,如果我们维护一个数组 $cnt$ 表示当前节点的乘法标记含有 $mod$ 的因子的个数,那么单点除法的时候只需要在叶结点暴力下放所有懒标记之后对 $cnt$ 进行修改就行了。 而区间乘法的时候就把 $x$ 拆成上述两部分,分别加到普通乘法标记和 $cnt$ 数组中即可。 其实你可以直接将 $cnt$ 数组理解为一个数(反正将它们快速幂后乘起来本来就是 $x$ 的一个因数),只是我们单独将它的分解形式拎出来储存了而已,这样在写代码的时候就方便你思考该对它干什么了。 ```cpp #include<bits/stdc++.h> #define int long long using namespace std; const int N = 1e5 + 10; int n, q, len; int p, phi, ys[12]; int exp(int a, int b){ int cnt = 1; while(b){ if(b & 1) (cnt *= a) %= p; (a *= a) %= p, b >>= 1; } return cnt; } struct NUM{ int x, cnt[12]; NUM(){} NUM(int n){ memset(cnt, 0, sizeof(cnt)); x = 0; if(n == 1){ x = 1; return ; } for(int i=1;i<=len;i++){ while(n % ys[i] == 0) n /= ys[i], cnt[i]++; }//将 n 拆成两部分 x = n; } int calc(){ int res = x; for(int i=1;i<=len;i++) (res *= exp(ys[i], cnt[i])) %= p; return res; }//算一算这个懒标记的总和到底是多少 } a[N], tagnum[N << 2]; int val[N << 2], tag[N << 2]; #define ls(o) (o << 1) #define rs(o) (o << 1 | 1) void build(int o, int l, int r){ tag[o] = 1; tagnum[o] = NUM(1); if(l == r){ val[o] = a[l].calc(); return ; } int mid = (l + r) >> 1; build(ls(o), l, mid); build(rs(o), mid + 1, r); val[o] = (val[ls(o)] + val[rs(o)]) % p; } void pushdown(int o){ (tag[ls(o)] *= tag[o]) %= p, (tag[rs(o)] *= tag[o]) %= p; (val[ls(o)] *= tag[o]) %= p, (val[rs(o)] *= tag[o]) %= p; (tagnum[ls(o)].x *= tagnum[o].x) %= p, (tagnum[rs(o)].x *= tagnum[o].x) %= p; for(int i=1;i<=len;i++){ tagnum[ls(o)].cnt[i] += tagnum[o].cnt[i]; tagnum[rs(o)].cnt[i] += tagnum[o].cnt[i]; tagnum[o].cnt[i] = 0; }//pushdown 的时候也要下传,cnt的本质和tag是一样的 tag[o] = tagnum[o].x = 1ll; } void update(int o, int l, int r, int s, int t, int v){ if(l >= s && r <= t){ NUM x = NUM(v); (tagnum[o].x *= x.x) %= p; (val[o] *= v) %= p; (tag[o] *= v) %= p; for(int i=1;i<=len;i++) tagnum[o].cnt[i] += x.cnt[i];//区间乘法更新 return ; } int mid = (l + r) >> 1; pushdown(o); if(s <= mid) update(ls(o), l, mid, s, t, v); if(t > mid) update(rs(o), mid + 1, r, s, t, v); val[o] = (val[ls(o)] + val[rs(o)]) % p; } void update(int o, int l, int r, int pos, int v){ if(l == r){ (a[l].x *= tagnum[o].x) %= p;//p和pos搞混了是谁我不说 tagnum[o].x = 1; for(int i=1;i<=len;i++){ a[l].cnt[i] += tagnum[o].cnt[i]; while(v % ys[i] == 0) a[l].cnt[i]--, v /= ys[i];//一个个把v的因子除掉 tagnum[o].cnt[i] = 0; } (a[l].x *= exp(v, phi - 1)) %= p;//剩下部分可以直接求逆元 val[o] = a[l].calc();//注意更新此时的单点和 return ; } int mid = (l + r) >> 1; pushdown(o); if(pos <= mid) update(ls(o), l, mid, pos, v); else update(rs(o), mid + 1, r, pos, v); val[o] = (val[ls(o)] + val[rs(o)]) % p; } int query(int o, int l, int r, int s, int t){ if(l >= s && r <= t) return val[o]; int mid = (l + r) >> 1; int res = 0; pushdown(o); if(s <= mid) (res += query(ls(o), l, mid, s, t)) %= p; if(t > mid) (res += query(rs(o), mid + 1, r, s, t)) %= p; return res; } main(){ scanf("%lld%lld", &n, &p); int m = p; phi = p; for(int i=2;i*i<=m;i++){ if(m % i == 0){ ys[++len] = i; phi = phi * (i - 1ll) / i;//顺便求一下 phi(mod) while(m % i == 0) m /= i; } } if(m > 1) ys[++len] = m, phi = phi * (m - 1ll) / m; for(int i=1,x;i<=n;i++){ scanf("%lld", &x); a[i] = NUM(x); } build(1, 1, n); scanf("%lld", &q); while(q--){ int op, l, r; int x; scanf("%lld", &op); if(op == 1){ scanf("%lld%lld%lld", &l, &r, &x); update(1, 1, n, l, r, x); } if(op == 2){ scanf("%lld%lld", &l, &x); update(1, 1, n, l, x); } if(op == 3){ scanf("%lld%lld", &l, &r); printf("%lld\n", query(1, 1, n, l, r)); } } return 0; } ```