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\) 暴力只打了两个性质,想出了一个不知道是不是正解的东西,但是没来得及写。

过了再回来写。

posted @ 2026-07-08 20:26  Ff472130  阅读(21)  评论(0)    收藏  举报