MX 暑假集训 7.8
已被神秘 IMO 模拟赛击败喵。
原题与模拟赛数据范围略有不同,以模拟赛为准。
T1:#6740.费马大定理
题意
给定质数 \(p\),问 \(1\) 到 \(L\) 中有多少个整数 \(k\),满足存在 \(x,y,z\) 使得 \(x^k+y^k \equiv z^k \pmod{p}\)。
\(2\le p\le 10^7+3,1\le L\le 10^{18}\)。
solution
赛时打了个表发现对于一个 \(k\) 若有解则存在 \(z=1\) 的解,于是打了 \(40\) pts 就跑路了。
挺有意思的题。
我们可以先把原根找出来,找原根可以从小到大枚举,判断对于 \(p-1\) 的每个质因子 \(x\) ,是否都有 \(g^{\frac{p-1}{x}} \not\equiv 1 \pmod{p}\),是则是一个原根,枚举的个数不会超过 \(p^{\frac{1}{4}}\)。
我们发现,对于一个 \(k\),\(x^k \equiv (g^a)^k \equiv g^{ak}\),则 \(x^k\) 一定可以写成 \(g^{ak}\) 的形式,即原根的 \(k\) 的倍数次方。
我们发现,若 \(\gcd(p-1,k)\) 相同,则 \(x^k\) 可取到的值也一定相同,所以我们只需要枚举 \(p-1\) 的因子,判断其是否合法,再乘上对应的数量即可。
如何快速判断一个数 \(k\) 是否合法呢?
合法的条件是 \(x^k+y^k \equiv z^k\),即 \(g^{ak}+g^{bk} \equiv g^{ck}\),两边乘以 \(g^{ck}\) 的逆元可得 \(g^{(a-c)k}+g^{(b-c)k} \equiv 1\)。
所以我们只需要找到 \(m,n\) 满足 \(g^{mk}+g^{nk} \equiv 1\) 则 \(k\) 合法,正好和赛时打表发现的规律一致。
对于每个 \(k\),我们把 \(g^{mk}\) 标记出来,再枚举 \(n\) 判断是否合法即可,这样对于每个 \(k\) 时间复杂度是 \(O(\frac{p}{k})\) 的。
接下来我们考虑如何统计 \(\gcd(p-1,k)=d\) 的 \(k\) 的个数。
\(L\) 以内 \(d\) 的倍数显然有 \(\left\lfloor \frac{L}{d} \right\rfloor\) 个,把是 \({kd}\ (k>1)\) 的倍数容斥掉即可。
Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<bitset>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
const int N=1e7+10;
const int inf=1e9+10;
int p;
ll L;
inline ll qpow(ll a,int b) {
ll res=1;
while (b) {
if (b&1) res=res*a%p;
a=a*a%p;b>>=1;
}
return res;
}
inline int get_gen(int p) {
vector<int> tmp;
bitset<N> vis;
int val=p-1;
f(i,2,val) {
if (vis[i]) continue;
tmp.push_back((p-1)/i);
while (val%i==0) val/=i;
for (int j=i+i;j<=val;j+=i) vis.set(j);
}
f(i,2,p) {
int flag=1;
for (int x:tmp) if (qpow(i,x)==1) {flag=0;break;}
if (flag) return i;
}
}
inline vector<int> get_d(int x) {
vector<int> ret;
for (int i=1;i*i<=x;i++) {
if (x%i) continue;
ret.push_back(i);
if (i*i!=x) ret.push_back(x/i);
}
sort(ret.begin(),ret.end());
reverse(ret.begin(),ret.end());
return ret;
}
bitset<N> vis;
inline bool check(int gen,int d) {
vector<ll> did;
for (ll k=qpow(gen,d),now=k;;now=now*k%p) {
if (vis[p+1-now]||p+1-now==now) {
for (ll x:did) vis.reset(x);
return 1;
}
if (vis[now]) break;
vis.set(now);
did.push_back(now);
}
for (ll x:did) vis.reset(x);
return 0;
}
struct Node{int d;ll v;};
vector<Node> did;
int main() {
freopen("fermat.in","r",stdin);
freopen("fermat.out","w",stdout);
scanf("%d%lld",&p,&L);
int gen=get_gen(p);
vector<int> D=get_d(p-1);
ll ans=0;
for (int d:D) {
ll num=L/d;
for (Node k:did) if (k.d%d==0) num-=k.v;
did.push_back({d,num});
ans+=check(gen,d)*num;
}
printf("%lld\n",ans);
return 0;
}
T2:青蛙思直线
题意
给定长度为 \(n\) 的操作序列,你有一个点,每个操作形如以下两种:
- 给定一条直线,将点变成其与直线的对称点;
- 给定一个点与一个角度,将你的点沿着给定的点旋转这个角度。
有共 \(m\) 次修改和询问操作,形式如下:
- 给定 \(x,y,l,r\),询问你的点初始时位于 \((x,y)\),在按顺序执行完 \([l,r]\) 内所有操作后得到的点;
- 给定一条直线与 \(l,r\),将操作序列 \([l,r]\) 内的直线和点都变成其与给定直线的对称点或直线;
- 给定一个点,一个角度与 \(l,r\),将操作序列 \([l,r]\) 内的直线和点都变成其与给定点旋转这个角度后得到的点或直线。
\(1\le n,m\le 10^5\)。
solution
神秘解析几何来袭。赛时根本不敢碰。
没咋听懂,好像是把每个点看作点复数 \(z=x+yi\),操作看成对复数做一堆运算,然后用个线段树维护起来,类似矩阵吧。
搞懂了再写。
T3:Happy Game
题意
给定 \(n\) 个点 \(m\) 条边的一张无向连通图,初始时点全为白色,对于每个点,你需要进行如下操作:
首先将这个点染成黑色,然后每次操作可以将黑点旁的一个或两个白点染黑,求最小操作次数使得将每个点染黑。
每个点的询问是独立的。
\(1\le n,m\le 5\times 10^6\)。
solution
记错时间了,没打 \(n^2\) 暴力只打了两个性质,想出了一个不知道是不是正解的东西,但是没来得及写。
过了再回来写。

浙公网安备 33010602011771号