2023.02.20【省选组】模拟 总结

\(\text{summary}\)

又是写暴力的一天

\(\text{T1【USACO 2020 US Open Platinum】 Exercise}\)

发现自己连 \(dp\) 都不会了
考场暴力都写不出来的原因:不知如何计算各个环长确定的情况下对应排列的数目
因为没有逆元,没法直接排列组合计数。。。
关键在于如果存在环长相同的情况,直接计数不可避免要除掉相同环长数量的阶乘
然而可以 \(dp\) 计数,考场想了一会却不会
考虑一个环的代表为这个环上最小的数,那么枚举当前最小值所在环的大小转移,这样就可以避免算重了

这道题又是 \(\text{lcm}\) 相关的计数
考虑单独拆开算每个质因子的指数,对某个质因子而言计算 \(lcm=p^c\) 的排列数量
改成计算 \(p^c|lcm\) 的排列数量,那么环中只要至少存在一个 \(p^c\) 倍数的环即可
补集思想,计数不存在一个环长为 \(p^c\) 倍数的排列数
若长度为 \(i\),这个数目记为 \(f_i\),那么 \(f_i=i!-\sum_{p^c|j} f_j g_{i-j}\)
\(g_i\) 表示长度为 \(i\) 的排列环长均为 \(p^c\) 的倍数的排列数目
于是就可以 \(O(n^2) \texttt{ } dp\)

\(\texttt{warning:}\) 加法运算整个式子的类型默认为第一个对象的类型

\(\text{Code}\)

$\text{Code}$
#include <bits/stdc++.h> 
#define IN inline
using namespace std;

typedef long long LL;
const int N = 7505;
int b[N], n, M, P, C[N][N], bz[N];

IN void Add(LL &x, int y){if ((x += y) >= P) x -= P;}

int fpow(int x, int y, int p) {
	int s = 1;
	for(; y; y >>= 1, x = (LL)x * x % p) if (y & 1) s = (LL)s * x % p;
	return s;
}

LL f[N], g[N], fac[N];
IN int calc(int x) {
	g[0] = 1;
	for(int i = x; i <= n; i += x) {
		g[i] = 0;
		for(int j = x; j <= i; j += x)
			Add(g[i], g[i - j] * C[i - 1][j - 1] % P * fac[j - 1] % P);
	}
	f[0] = 1;
	for(int i = n % x; i <= n; i += x) {
		f[i] = fac[i];
		for(int j = x; j <= i; j += x)
			Add(f[i], P - g[j] * C[i][j] % P * f[i - j] % P);
	}
	return (fac[n] - f[n] + P) % P;
}

int main() {
	freopen("exercise.in", "r", stdin);
	freopen("exercise.out", "w", stdout);
	scanf("%d%d", &n, &M), P = M - 1, fac[0] = 1;
	for(int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % P;
	for(int i = 0; i <= n; i++) C[i][0] = 1;
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= i; j++) {
			LL z = C[i - 1][j]; Add(z, C[i - 1][j - 1]);
			C[i][j] = z;
		}
	int ans = 1;
	for(int i = 2; i <= n; i++) if (!bz[i]) {
		for(int j = i * i; j <= n; j += i) bz[j] = 1;
		for(int j = i; j <= n; j *= i) ans = (LL)ans * fpow(i, calc(j), M) % M;
	}
	printf("%d\n", ans);
}

\(\text{T2 6517. 【GDOI2020模拟03.28】数三数(three)}\)

仙姑了。。。

\(\text{T3 6568. 【GDOI2020模拟】 迫害 DJ (hakugai)}\)

\(g\)\(p\) 意义下是有个循环节的,且比较短,和斐波那契数列循环节一样
找到这个循环节即可,然后就是套路矩乘了

\(\texttt{warning}:\) 函数定义的 \(void\) 不能漏,本地编译却可通过

\(\text{Code}\)

$\text{Code}$
#include <bits/stdc++.h>
#define IN inline
using namespace std;

template <typename Tp>
IN void read(Tp &x) {
	x = 0; char ch = getchar(); int f = 0;
	for(; !isdigit(ch); f = (ch == '-' ? 1 : f), ch = getchar());
	for(; isdigit(ch); x = (x<<3)+(x<<1)+(ch^48), ch = getchar());
	if (f) x = ~x + 1;
}

typedef long long LL;
LL P, pk[105];
LL gcd(LL x, LL y){return (!y ? x : gcd(y, x % y));}

struct Matrix {
	LL s00, s01, s10, s11;
	IN Matrix(LL a0 = 0, LL a1 = 0, LL a2 = 0, LL a3 = 0){s00 = a0, s01 = a1, s10 = a2, s11 = a3;}
	IN Matrix operator * (const Matrix &B) {
		Matrix C;
		C.s00 = (s00 * B.s00 % P + s01 * B.s10 % P) % P;
		C.s01 = (s00 * B.s01 % P + s01 * B.s11 % P) % P;
		C.s10 = (s10 * B.s00 % P + s11 * B.s10 % P) % P;
		C.s11 = (s10 * B.s01 % P + s11 * B.s11 % P) % P;
		return C;
	}
}Z, I, B;

IN Matrix fpow(Matrix x, LL y) {
	Matrix s = I;
	for(; y; y >>= 1, x = x * x) if (y & 1) s = s * x;
	return s;
}

IN int find(int x) {
	if (x == 2) return 3;
	if (x == 3) return 4;
	if (x == 5) return 10;
	if (x % 5 == 1 || x % 5 == 4) return x - 1;
	return x + 1;
}
IN LL calc(LL p) {
	LL lcm = 1;
	for(LL i = 2; i * i <= p; i++) if (p % i == 0) {
		LL w = 1;
		while (p % i == 0) p /= i, w *= i;
		w /= i, w = w * find(i), lcm = lcm / gcd(lcm, w) * w;
	}
	if (p > 1) p = find(p), lcm = lcm / gcd(lcm, p) * p;
	return lcm;
}

int main() {
	freopen("hakugai.in", "r", stdin);
	freopen("hakugai.out", "w", stdout);
	int t; read(t); I = Matrix(1, 0, 0, 1);
	for(; t; --t) {
		int a, b, p, k; LL n; read(a), read(b), read(n), read(k), read(p);
		pk[1] = p;
		for(int i = 2; i <= k; i++) pk[i] = calc(pk[i - 1]);
		for(Matrix ret; k; k--) {
			P = pk[k], Z = Matrix(a % P, b % P, 0, 0), B = Matrix(0, P - 1, 1, 3);
			ret = Z * fpow(B, n), n = ret.s00;
		}
		printf("%lld\n", n);
	}
}

这个循环节的计算规律是真不可背诵的
不过可以随机化,依据生日悖论寻找,\(O(\sqrt P)\)
具体来说就是猜测最小循环节上界,大致猜个 \(12P\)
然后在这个范围内随机个 \(i\),看算出的 \((F_i,F_{i+1})\) 是否与之前重复
若是,重复的为 \((F_j,F_{j+1})\),那么一定有个循环节 \(|i-j|\)
期望 \(O(\sqrt P)\) 次可以找到

下面是 \(P4000\) 斐波那契数列
交了三遍才过呢

\(\text{Code}\)

$\text{Code}$
#include <bits/stdc++.h>
#define IN inline
using namespace std;

typedef long long LL;
typedef unsigned long long ull;

LL P;

struct Matrix {
	LL s00, s01, s10, s11;
	IN Matrix(LL a0 = 0, LL a1 = 0, LL a2 = 0, LL a3 = 0){s00 = a0, s01 = a1, s10 = a2, s11 = a3;}
	IN Matrix operator * (const Matrix &B) {
		Matrix C;
		C.s00 = (s00 * B.s00 % P + s01 * B.s10 % P) % P;
		C.s01 = (s00 * B.s01 % P + s01 * B.s11 % P) % P;
		C.s10 = (s10 * B.s00 % P + s11 * B.s10 % P) % P;
		C.s11 = (s10 * B.s01 % P + s11 * B.s11 % P) % P;
		return C;
	}
}I, B;

const int M = 1 << 18;
struct KSM {
	Matrix f[M + 5], ff[M + 5];
	IN void init(int m) {
		f[0] = ff[0] = I;
		for(int i = 1; i <= m; i++) f[i] = f[i - 1] * B;
		for(int i = 1; i <= m; i++) ff[i] = ff[i - 1] * f[m];
	}
	IN Matrix pow(LL x){return f[x & (M - 1)] * ff[x >> 18];}
}F;

const int N = 3e7 + 5;
char str[N];
mt19937_64 rnd(time(0));

unordered_map<ull, LL> hs;

int main() {
	scanf("%s%d", str, &P), I = Matrix(1, 0, 0, 1), B = Matrix(0, 1, 1, 1), F.init(M);
	LL len = 0;
	while (1) {
		LL x = (rnd() << 28 >> 28); Matrix C = F.pow(x);
		ull val = ((ull)C.s00 << 32) | C.s01;
		if (hs.find(val) != hs.end()){len = abs(x - hs[val]); break;}
		hs[val] = x;
	}
	LL z = 0;
	for(int i = 0; i < strlen(str); i++) z = (z * 10 + (str[i] ^ 48)) % len;
	printf("%lld\n", F.pow(z).s01);
}
posted @ 2023-02-20 20:20  leiyuanze  阅读(21)  评论(0编辑  收藏  举报