【BUAA Spring Training 02】二分答案 | 矩阵快速幂 | 质因数 | GCD | Nim博弈 | 数位dp | 莫比乌斯反演

第二场春训,谢谢zlc和pmxm学长 qwq!
数论只会GCD的我枯了 qwq )


【BUAA Spring Training 02】


Tags:二分答案 快速幂 GCD Nim博弈 数位dp 莫比乌斯反演




Problem A. 搭积木


[A] 题意

n + m n+m n+m 个人用积木搭堆(每个人搭一堆), n n n 个人只能用高度为 2 2 2 的积木, m m m 个人只能用高度为 3 3 3 的积木。两种积木均有无限多个。

在找出在所有人搭出的积木高度都不同的情况下,求搭出的最高的堆的高度的最小值。


[A] 思路

最大最小,二分答案。

时间复杂度(check 是 O ( 1 ) O(1) O(1) 的): O ( l o g ( max ⁡ { n , m } ) ) O(log(\max\{n, m\})) O(log(max{n,m}))



[A] 代码

#include <cstdio>

#define GC getchar()
#define _SN(x) {char _c=GC,_v=1;for(x=0;_c<48||_c>57;_c=GC)if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=GC);if(_v==-1)x=-x;}
#define _SAN(a,n) {auto _i=0,_n=n;for(;_i<_n;++_i)_SN(a[_i])}
#define _SA(a,l,r) {auto _i=l,_r=r;for(;_i<_r;++_i)_SN(a[_i])}
#define _gS(_1, _2, _3, _sc, ...) _sc
#define sc(...) _gS(__VA_ARGS__,_SA,_SAN,_SN, ...)(__VA_ARGS__)
#define PC putchar
template<class T>
void UPRT(const T _){if(_>=10)UPRT(_/10);PC(_%10+48);}

int need2, need3;

bool check(const int ans)
{
	int all6 = ans / 6;
	int all2 = ans / 2 - all6;
	int all3 = ans / 3 - all6;

	int tot = 0;
	if (all2 < need2)
		tot += need2 - all2;
	if (all3 < need3)
		tot += need3 - all3;

	return all6 >= tot;
}


int main()
{
#ifdef _KEVIN
	freopen("in.txt", "r", stdin);
#endif

	sc(need2)sc(need3)

	int L = 0, R = 6.6e6;	// 暴力了暴力了 qwq
	while (L <= R)
	{
		auto mid = (L+R) >> 1;
		if (check(mid))
			R = mid-1;
		else
			L = mid+1;
	}
	UPRT(L);

	return 0;
}




Problem B. 线性迭代函数


[B] 题意

给定整数 A 、 B 、 n 、 x A、B、n、x ABnx,令 f ( x ) = = A x + B f(x) == Ax + B f(x)==Ax+B,求 g ( n ) ( x )   %   ( 1 0 9   +   7 ) g^{(n)}(x)\ \%\ (10^9 + 7) g(n)(x) % (109+7)

1   ≤   A ,   B ,   x   ≤   1 0 9 ,       1   ≤   n   ≤   1 0 18 1 ≤ A, B, x ≤ 10^9,\ \ \  1 ≤ n ≤ 10^{18} 1A,B,x109,   1n1018

其中:

  • g ( 0 ) ( x ) = = x g^{(0)}(x) == x g(0)(x)==x
  • g ( n ) ( x ) = = f ( g ( n − 1 ) ( x ) ) g^{(n)}(x) == f(g^{(n-1)}(x)) g(n)(x)==f(g(n1)(x))

[B] 思路

(幂指数都明确写出来了qwq)矩阵快速幂。

时间复杂度: O ( 2 3 × log ⁡ ( n ) ) O(2^3\times \log(n)) O(23×log(n))


[B] 代码

/*
 *      If we give,
 *      all we've got,
 *      we will make it through.
 */


#include <cstdio>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <ctime>
#include <string>
#include <algorithm>
#include <vector>
#include <list>
#include <map>
#include <set>

#define GC getchar()
#define _SN(x) {char _c=GC,_v=1;for(x=0;_c<48||_c>57;_c=GC)if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=GC);if(_v==-1)x=-x;}
#define _SAN(a,n) {auto _i=0,_n=n;for(;_i<_n;++_i)_SN(a[_i])}
#define _SA(a,l,r) {auto _i=l,_r=r;for(;_i<_r;++_i)_SN(a[_i])}
#define _gS(_1, _2, _3, _sc, ...) _sc
#define sc(...) _gS(__VA_ARGS__,_SA,_SAN,_SN, ...)(__VA_ARGS__)
#define _G1(_1) int _1;sc(_1)
#define _G2(_1,_2) int _1,_2;sc(_1)sc(_2)
#define _G3(_1,_2,_3) int _1,_2,_3;sc(_1)sc(_2)sc(_3)
#define _gG(_1,_2,_3,_get, ...) _get
#define get(...) _gG(__VA_ARGS__,_G3,_G2,_G1, ...)(__VA_ARGS__)
#define _F0N(i,n) for(auto i=0;i<n;++i)
#define _FLR(i,l,r) for(auto i=l,_r=r;i<_r;++i)
#define _gF(_1, _2, _3, _F, ...) _F
#define F(...) _gF(__VA_ARGS__,_FLR,_F0N, ...)(__VA_ARGS__)
#define _FD0(i,n) for(auto i=0;i<=n;++i)
#define _FDL(i,l,r) for(auto i=l,_r=r;i<=_r;++i)
#define _gFD(_1, _2, _3, _FD, ...) _FD
#define FD(...) _gFD(__VA_ARGS__,_FDL,_FD0, ...)(__VA_ARGS__)

#define IL inline
#define LL long long
#define ULL unsigned LL
#define PC putchar
template<class T>
void PRT(const T _){if(_<0){PC(45),PRT(-_);return;}if(_>=10)PRT(_/10);PC(_%10+48);}
template<class T>
void UPRT(const T _){if(_>=10)UPRT(_/10);PC(_%10+48);}


const LL MOD = 1000000007LL;
template <const int R, const int C, typename vint = LL>
class Mat
{
public:
	vint m[R][C];

public:
	inline void clear(void)
	{
		memset(m, 0, sizeof(m));
	}

	void init(void)
	{
		memset(m, 0, sizeof(m));
		for (int i=0; i<R; ++i)
			m[i][i] = 1;
	}

	template <const int C1>
	Mat<R, C1> operator *(const Mat<C, C1> &o) const
	{
		Mat<R, C1> ret;
		for (int r=0; r<R; ++r)
			for (int c=0; c<C1; ++c)
			{
				ret.m[r][c] = ((*m[r] % MOD) * ((*o.m)[c] % MOD)) % MOD;
				for (int k=1; k<C; ++k)
				{
					ret.m[r][c] += ((m[r][k] % MOD) * (o.m[k][c] % MOD)) % MOD;
					ret.m[r][c] %= MOD;
				}
			}
		return ret;
	}

	const Mat<R, R> &pow(LL n)
	{
		Mat<R, R> tp, a(*this);
		this->init();
		while (n)
		{
			if(n&1)
			{
				for (int r=0; r<R; ++r)
					for (int c=0; c<R; ++c)
					{
						tp.m[r][c] = ((*m[r] % MOD) * ((*a.m)[c] % MOD)) % MOD;
						for (int k=1; k<R; ++k)
						{
							tp.m[r][c] += ((m[r][k] % MOD) * (a.m[k][c] % MOD)) % MOD;
							tp.m[r][c] %= MOD;
						}
							
					}
				(*this) = tp;
			}
			for (int r=0; r<R; ++r)
				for (int c=0; c<R; ++c)
				{
					tp.m[r][c] = ((*a.m[r] % MOD) * ((*a.m)[c] % MOD)) % MOD;
					for (int k=1; k<R; ++k)
					{
						tp.m[r][c] += ((a.m[r][k] % MOD) * (a.m[k][c] % MOD)) % MOD;
						tp.m[r][c] %= MOD;
					}
				}
			a = tp;
			n >>= 1;
		}
		return *this;
	}

	inline vint * operator [](const unsigned idx)
	{
		return m[idx];
	}
};


int main()
{
#ifdef _KEVIN
	freopen("in.txt", "r", stdin);
#endif
	
	LL A, B, n, x;
	sc(A)sc(B)
	sc(n)sc(x)

	if (n == 0)
		return PRT(x), 0;

	Mat<2, 2> a;
	Mat<2, 1> b;

	a[0][0] = 1, a[0][1] = 0;
	a[1][0] = B, a[1][1] = A;

	b[0][0] = 1,
	b[1][0] = (A*x%MOD + B) % MOD;

	Mat<2, 1> ans = a.pow(n-1) * b;
	PRT(ans[1][0] % MOD);
	
	return 0;
}




Problem C. 简单求和


[C] 题意

给定一个数组 a [   ] a[\ ] a[ ],长度为 n ≤ 1 0 5 n \le 10^5 n105,每一项 ∈ ( 0 , 10000 ] \in (0, 10000] (0,10000]

定义 f ( l , r ) f(l, r) f(l,r) 表示满足以下条件的 i i i 的个数:

  • i ∈ [ l , r ] i \in [l, r] i[l,r]
  • ∀ j ∈ [ l , r ]    a n d    j   ! = i ,   s . t .    a [ i ] % a [ j ]   ! = 0 \forall j\in [l, r]\ \ and\ \ j\ != i,\ s.t.\ \ a[i]\%a[j]\ !=0 j[l,r]  and  j !=i s.t.  a[i]%a[j] !=0

∑ l = 1 n ∑ r = 1 n f ( l , r )   %   ( 1 0 9 + 7 ) \sum\limits_{l=1}^{n} \sum\limits_{r=1}^{n} f(l, r)\ \%\ (10^9+7) l=1nr=1nf(l,r) % (109+7)


[C] 思路

这个数据范围很微妙呀…

先在纸上推一推例子,发现可以逐项统计每项的贡献。

对于 a [ i ] a[i] a[i],记其从 i i i 最多向左延伸到 l e f t left left,使得 a [ l e f t   . . .   i − 1 ] a[left\ ...\ i-1] a[left ... i1] i − l e f t i-left ileft 个数都不是 a [ i ] a[i] a[i] 的因子,

然后同理记其最多向右延伸到 r i g h t right right。那么 a [ i ] a[i] a[i] 对整体求和式的贡献就是 l e f t × r i g h t left \times right left×right [ l e f t   . . .   i − 1 ] [left\ ...\ i-1] [left ... i1] [ i + 1   . . .   r i g h t ] [i+1\ ...\ right] [i+1 ... right] 各自任选一项构成的区间都对这个 i i i 满足题目条件)


如果预处理好 ( 0 , 10000 ] (0, 10000] (0,10000] 的质因数,然后分别从左往右和从右往左扫一遍(同时记录数字最近位置)的话,复杂度就是 O ( ∑ i n s q r t ( a [ i ] ) ) O(\sum\limits_{i}^{n}sqrt(a[i])) O(insqrt(a[i])) 的。(放大一点就是 O ( n   s q r t ( 10000 ) ) O(n\ sqrt(10000)) O(n sqrt(10000)) 的)



[C] 代码

#include <cstdio>
#include <cstring>
#include <vector>

#define _Q_(x) {register char _c=getchar(),_v=1;for(x=0;_c<48||_c>57;_c=getchar())if(_c==45)_v=-1;
#define _H_(x) for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=getchar());if(_v==-1)x=-x;}
#define sc(x) _Q_(x)_H_(x)
#define se(x) _Q_(x)else if(_c==EOF)return 0;_H_(x)

#define _G1(_1) int _1;sc(_1)
#define _G2(_1,_2) int _1,_2;sc(_1)sc(_2)
#define _G3(_1,_2,_3) int _1,_2,_3;sc(_1)sc(_2)sc(_3)
#define _gG(_1,_2,_3,_get, ...) _get
#define get(...) _gG(__VA_ARGS__,_G3,_G2,_G1, ...)(__VA_ARGS__)
#define _F0N(i,n) for(auto i=0;i<n;++i)
#define _FLR(i,l,r) for(auto i=l,_r=r;i<_r;++i)
#define _gF(_1, _2, _3, _F, ...) _F
#define F(...) _gF(__VA_ARGS__,_FLR,_F0N, ...)(__VA_ARGS__)
#define _FD0(i,n) for(auto i=0;i<=n;++i)
#define _FDL(i,l,r) for(auto i=l,_r=r;i<=_r;++i)
#define _gFD(_1, _2, _3, _FD, ...) _FD
#define FD(...) _gFD(__VA_ARGS__,_FDL,_FD0, ...)(__VA_ARGS__)

#define IL inline
#define LL long long
#define ULL unsigned LL
#define PC putchar
template<class T>
void PRT(const T _){if(_<0){PC(45),PRT(-_);return;}if(_>=10)PRT(_/10);PC(_%10+48);}
template<class T>
void UPRT(const T _){if(_>=10)UPRT(_/10);PC(_%10+48);}

const int MN = 100005, MV = 10001, MSQ = 101;
const LL MOD = 1000000007LL;

int a[MN];
int l_len[MN];
int r_len[MN];

std::vector<int> vv[MV];
int latest[MV];

void fr(const int n)
{
	for(int i=1; i*i<=n; ++i)
	{
		if (n % i == 0)
		{
			vv[n].emplace_back(i);
			if (i*i != n)
				vv[n].emplace_back(n/i);
		}
	}
}


int main()
{
#ifdef _KEVIN
	freopen("in.txt", "r", stdin);
#endif
	
	for (int i=1; i<=10001; ++i)
		fr(i);
	
	while (1)
	{
		int n;
		se(n)
		for (int i=1; i<=n; ++i)
			sc(a[i])
		
		memset(latest, 0, sizeof(latest));
		for (int i=1; i<=n; ++i)
		{
			int max = 0;
			for (int j=0, sz=vv[a[i]].size(); j<sz; ++j)
				if (latest[vv[a[i]][j]] > max)
					max = latest[vv[a[i]][j]];
			l_len[i] = i-max;
			latest[a[i]] = i;
		}
		
		memset(latest, 0x3f, sizeof(latest));
		for (int i=n; i>=1; --i)
		{
			int min = n+1;
			for (int j=0, sz=vv[a[i]].size(); j<sz; ++j)
				if (latest[vv[a[i]][j]] < min)
					min = latest[vv[a[i]][j]];
			r_len[i] = min-i;
			latest[a[i]] = i;
		}
		
		LL sum = 0;
		for (int i=1; i<=n; ++i)
		{
			sum += ((LL)l_len[i]%MOD) * ((LL)r_len[i]%MOD) %MOD;
			if (sum >= MOD)
				sum -= MOD;
		}
		
		UPRT(sum), PC(10);
	}
		
	return 0;
}




Problem D. 构造


[D] 题意

给你一个数 2 ≤ n ≤ 1 0 9 2\le n \le 10^9 2n109

要你拿出一些真分数来(要求分母都 &lt; n &lt; n <n,分子都 ≥ 1 \ge 1 1)让这些真分数的和等于 n − 1 n \frac{n-1}{n} nn1


[D] 思路

举几个例子就会发现,只要把这个 n n n 分解出质因数然后去重,如果去重后质因数的个数大于等于 2 2 2,然后任选其中两个记为 p , q p,q pq,则一定可以找到 x , y x,y xy,使得 x ∗ p + y ∗ q   = =   n − 1 x*p + y*q\ ==\ n-1 xp+yq == n1(因为互质的两个数 a , b a,b ab 最大不能表示出的正整数是 a ∗ b − a − b a*b-a-b abab,而显然 p ∗ q − p − q &lt; p ∗ q ≤ n p*q-p-q &lt; p*q \le n pqpq<pqn,所以 p ∗ q − p − q &lt; n − 1 p*q-p-q &lt; n-1 pqpq<n1,所以能表示出 n − 1 n-1 n1

然后两个分数就是 x ∗ p n \frac{x*p}{n} nxp y ∗ q n \frac{y*q}{n} nyq

再约分一下就符合题目要求了(分母小于 n n n 的真分数)

实际凑系数的时候可以拿最大的那个质因数为第一个开始凑,由于质因数必 ≤ s q r t ( n ) \le sqrt(n) sqrt(n),故最多凑 s q r t ( n ) sqrt(n) sqrt(n) 次。

分解质因数的复杂度也是 O ( s q r t ( n ) ) O(sqrt(n)) O(sqrt(n)),所以总时间复杂度也是 O ( s q r t ( n ) ) O(sqrt(n)) O(sqrt(n))



[D] 代码

#include <cstdio>
#include <cmath>
#include <vector>
#include <algorithm>


std::vector<int> factors;

void dc(int n)
{
	factors.clear();
	for (int i=2; i*i<=n; i++)
	{
		while (n != i)
		{
			if (n % i == 0)
			{
				factors.emplace_back(i);
				n /= i;
			}
			else
				break;
		}
	}
	factors.emplace_back(n);
	std::sort(factors.begin(), factors.end());
	factors.erase(std::unique(factors.begin(), factors.end()), factors.end());
}

void prt_ans(const int n)
{
	const int p = factors.back(), q = factors.front();
	int x, y;
	for (x=1; x<=n; ++x)
	{
		int res = n-1 - p*x;
		if (res && res % q == 0)
		{
			y = res / q;
			break;
		}
	}
	puts("2");
	int gp = std::__gcd(p*x, n), gq = std::__gcd(q*y, n);
	printf("%d %d\n", p*x / gp, n / gp);
	printf("%d %d\n", q*y / gq, n / gq);
}


int main()
{
	int n;
	while (~scanf("%d", &n))
	{
		dc(n);
		int amt = factors.size();
		if (amt >= 2)
		{
			puts("YES");
			prt_ans(n);
		}
		else
			puts("NO");
	}

	return 0;
}




Problem E. 石头游戏


[E] 题意

(又是)Alice 和 Bob 玩游戏。他们玩取石子游戏。和基本的取石子游戏不同的是,对于每一堆石子,如果曾经取过 a a a 个石子,那么以后取的时候就不能再取 a a a 个了。

Alice 先手,请问 Alice 是否必败?


[E] 思路

这个和朴素 Nim 博弈不同的是,取石子不是完全自由的。但可以考虑到的是,为了尽可能多地取石子,每堆石子等价的取的次数是 1 + 2 + 3 + . . . 1+2+3+... 1+2+3+...,因此可以预处理出每堆石子的等价SG函数值,然后再异或等价值。如果异或和是 0 0 0 则必败。

时间复杂度:遍历 O ( n ) O(n) O(n) 的。(最大石子数很小,预处理时间可以忽略)



[E] 代码

#include <cstdio>
#include <cstring>

constexpr int MG(66);

int SG[MG+7];

int sg(const int x)
{
	if (~SG[x]) return SG[x];

	int pre = sg(x-1) + 1;
	pre = pre * (pre+1) / 2;
	return SG[x] = (pre==x ? SG[x-1]+1 : SG[x-1]);
}


int main()
{
	memset(SG, -1, sizeof(SG)), SG[0] = 0;

	for (int x=1; x<=MG; ++x)
		if (SG[x] == -1)
			sg(x);
	
	int xor_s = 0;
	int T;
	scanf("%d", &T);
	{
		int si;
		scanf("%d", &si);
		xor_s xor_eq SG[si];
	}
	
	puts(xor_s ? "NO" : "YES");
	
	return 0;
}




Problem F. 数位和


[F] 题意

给定区间 [ A , B ] [A, B] [A,B] 1 ≤ A ≤ B ≤ 1 0 9 1 \le A \le B \le 10^9 1AB109),求区间内有多少数,其能整除其数位和


[F] 思路

数位 d p dp dp 裸题。由于最大数位和不超过 81 81 81,所以可以枚举数位和,然后把各个结果求和即可(不用去重,因为一个数不可能有多个不相等的数位和)。

注意这里又有值取模又有数位和,还要枚举数位和,所以 d p dp dp 数组应该要有1+3=4维…

时间复杂度:(…发现不会分析了 )大不了就是整个 d p dp dp 数组的大小嘛!数量级 10 ∗ 81 ∗ 81 ∗ 81 ∼ 5 e 6 10*81*81*81 \sim 5e6 108181815e6


另外关于数位 d p dp dp 的详细分析和例题可以戳这里o



[F] 代码

#include <cstdio>
#include <cstring>
#define MS 82
#define ML 11

int num[ML];
inline int scan(int x)
{
	int top = 0;
	do num[++top] = x % 10;
	while (x /= 10);
	return top;
}


int GOAL;
int dp[MS][ML][MS][MS];
int dfs(const int len, const int d_sum, const int r, const bool lim)
{
	if (!len)
		return d_sum==GOAL && r==0;
	if (!lim && ~dp[GOAL][len][d_sum][r])
		return dp[GOAL][len][d_sum][r];

	int sum = 0;
	const int ub = lim ? num[len] : 9;
	for (int x=0; x<=ub; ++x)
		sum += dfs(len-1, d_sum+x, (r*10+x) % GOAL, lim && x==ub);
	return lim ? sum : dp[GOAL][len][d_sum][r] = sum;
}


inline int count(const int N)
{
	int sum = 0;
	const int len = scan(N);
	for (GOAL=1; GOAL<MS; ++GOAL)
		sum += dfs(len, 0, 0, true);
	return sum;
}


int main()
{
	printf("%d\n", 10*81*81*81);
	memset(dp, -1, sizeof(dp));

	int T;
	scanf("%d", &T);
	for (int i=1; i<=T; ++i)
	{
		int L, R;
		scanf("%d %d", &L, &R);
		printf("Case %d: %d\n", i, count(R) - count(L-1));
	}

	return 0;
}




Problem G. 最小公倍数之和


[G] 题意


[G] 思路



[G] 代码





继续加油吧~ (期待认识即将一起奋斗的小伙伴们hhh)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值