GZM毒瘤数论DAY2——2019暑假篇

目录

一.T1

1.题目

1.题目描述

2.输入

3.输出

4.样例输入1

5.样例输出1

6.样例输入2

7.样例输出2

8.数据范围

2.题解

3.Code

二.T2

1.题目

1.题目描述:

2.输入

3.输出

4.样例输入

5.样例输出

6.样例解释

7.数据范围

2.题解

3.Code

三.T3

1.题目

1.题目描述

2.样例输入1

3.样例输出1

4.样例输出2

5.样例输出2

6.样例解释

7.数据范围

2.题解

3.Code

谢谢!


毒瘤毒瘤毒瘤毒瘤毒瘤毒瘤毒瘤毒瘤毒瘤毒瘤毒瘤毒瘤毒瘤毒瘤毒瘤

一.T1

1.题目

1.题目描述

有一n*m的棋盘,每次随机染黑一个位置(可能染到已经黑了的),当某一行或者一列全为黑色时停止,求期望染色次数(mod 998244353)

2.输入

一行两个正整数n,m

3.输出

期望结果

4.样例输入1

2 2

5.样例输出1

3

6.样例输入2

10 20

7.样例输出2

397903748

8.数据范围

对于20%的数据n,m<=5

对于100%的数据n,m<=1000

2.题解

这道题目的重点就在于数学期望,首先要懂得什么叫做期望(点击打开链接)

好,那么来说说这道题目的期望怎么算。

首先,指定一个格子,选中它的概率就是1/(n + m),然后取倒,也就期望(n + m)次能抽到它;

如果是两个格子,就是选中一个格子的期望步数+选中两个格子的期望步数:(n + m) + (n + m) / 2;

以此类推,后面就都是这样。

然后处理总期望,就是枚举选出的行和列,加起来是奇数就加,偶数就减它的期望值,这是容斥

最后输出答案就行了。

3.Code

//wait
#include <cstdio>
#include <cstring>
#include <vector>
#include <map>
#include <cmath>
#include <queue>
#include <stack>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <ctime>
using namespace std;

#define mod 998244353
#define M 1000005
#define LL long long

int n, m;
LL fac[M], inv[M], f[M], ans;

void prepare (int x){
	fac[1] = fac[0] = inv[1] = inv[0] = 1;
	f[1] = n * m;//概率
	for (int i = 2; i <= x; i ++){
		fac[i] = 1ll * fac[i - 1] * i % mod;
		inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
		f[i] = 1ll * (f[i - 1] + 1ll * n * m * inv[i] % mod) % mod;
	}
	for (int i = 2; i <= x; i ++){
		inv[i] = 1ll * inv[i - 1] * inv[i] % mod;
	}
}
LL C (LL a, LL b){
	return 1ll * fac[b] * inv[b - a] % mod * inv[a] % mod;
}
int main (){
	//freopen ("wait.in", "r", stdin);
	//freopen ("wait.out", "w", stdout);
	scanf ("%d %d", &n, &m);
	prepare (n * m);
	for (int i = 0; i <= n; i ++){
		for (int j = 0; j <= m; j ++){
			if (i || j){
				LL a = i * m + j * n - i * j;
				LL now = 1ll * C (i, n) * C (j, m) % mod * f[a] % mod;
				if ((i + j) % 2 == 1)
					ans = (ans + now) % mod;
				else
					ans = ((ans - now) % mod + mod) % mod;
			}
		}
	}
	printf ("%lld\n", ans);
	return 0;
}

二.T2

1.题目

1.题目描述:

双方进行游戏,有两个正整数a,b

若a<=b

双方轮流进行选择下列两种操作之一

1)将b对a取模,即b变为b%a

2)从b中减去a的幂,不能减成负数,即b变为b-a^k(b-a^k>=0且k为正整数)

若a>b,类似

若a,b中之一为0,则无法进行,无法进行者败

输入初始a,b,判断先后手谁有必胜策略

2.输入

第一行一个正整数T

接下来T行每行两个正整数a,b

3.输出

对于每个数据输出"First"或"Second"表示先手必胜或后手必胜

4.样例输入

4

10 21

31 10

0 1

10 30

5.样例输出

First

Second

Second

First

6.样例解释

在样例一中,第一个玩家只能变换到(11,10)。然后在第二个玩家变换到(1,10)后,第一个玩家可以将10对1取余就赢了。

在样例二中,第一个玩家可以变换到(1,10)或(21,10),不管哪种,第二个玩家都赢。

在样例三中,第一个玩家无法操作。

在样例四中,第一个玩家直接对30进行取余,就赢了。

7.数据范围

对于30%的数据,a,b<=1000

对于100%的数据,a,b<=10^18,T<=10^3

2.题解

这道题目很玄学,要用到数学归纳法,或者说找规律,可以了解一下数学归纳法(点击打开链接)

可以从题意得出,每次有两种操作情况:1.模;2.减

1.模,就直接模,如果模了之后先手必败,那么现在你就先手必胜。

2.减,如果你一个一个地减,包超时,这时候,就是找规律的时候了(数学归纳法),怎么找呢,就是找一个例子,把小的那一个数一直扩倍,当大的数减去小的数扩倍之后先手必数就记为1,因为这代表你现在可以先手必胜,反之,就记为0;

这样搞了之后,你会找到一个规律,就是:

1.如果小的数是奇数,那么循环节就是10;

2.如果小的数是偶数,那么就是(小的数+1)一个循环节,

比如说,记小的数为a,若a=2,循环节:101,若a=4,循环节:10101

记小的数为a,还有一个结论:a^2 % (a + 1) = 1(二次探测定理)

好,发现了这个玩意,就可以解决问题了。

3.Code

//game
#include <map>
#include <cmath>
#include <queue>
#include <stack>
#include <ctime>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

#define LL long long

LL a, b, T;

bool solve_it (LL x, LL y){
	if (! x)
		return 0;
	if (! solve_it (y % x, x))
		return 1;
	LL k = (y / x - 1) % (x + 1);
	if (k % 2 == 1 || k == x)
		return 1;
	else
		return 0;
}
int main (){
	//freopen ("game.in", "r", stdin);
	//freopen ("game.out", "w", stdout);
	scanf ("%lld", &T);
	while (T --){
		scanf ("%lld %lld", &a, &b);
		if (a > b)
			swap (a, b);
		if (solve_it (a, b))
			printf ("First\n");
		else
			printf ("Second\n");
	}
	return 0;
}

三.T3

1.题目

1.题目描述

有两个1~n的排列A,B,序列C一开始为空,每次可以选择进行以下两种操作之一

1)若A不为空,则可取出A的开头元素放在序列C的末尾

2)若B不为空,则可取出B的开头元素放在序列C的末尾

这样当A,B皆为空时,C称为排列A,B的合并,其长度为2*n

记F(A,B)为A,B的所有可能合并的总数

求对于所有可能的1~n的排列A,B,F(A,B)的和,mod 998244353

2.样例输入1

2

3.样例输出1

12

4.样例输出2

10

5.样例输出2

701089156

6.样例解释

对于样例1:F(12,12)=2(1122,1212),F(21,21)=2(2211,2121),F(12,21)=F(21,12)=4(1221,1212,2112,2121)

7.数据范围

对于20%的数据,n<=5

对于40%的数据,n<=20

对于100%的数据,n<=100

2.题解

就一个DP转移,重在去重,g存完美后缀,减去各种完美后缀的情况就行了。

3.Code

//R
#include <map>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <ctime>
#include <vector>
#include <stack>
#include <queue>
using namespace std;

#define LL long long
#define mod 998244353

int n;
LL fac[205], inv[205], g[205], f[105][105][105];

void prepare (){
	fac[1] = fac[0] = inv[1] = inv[0] = 1;
	for (int i = 2; i <= n * 2; i ++){
		fac[i] = 1ll * fac[i - 1] * i % mod;
		inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
	}
	for (int i = 2; i <= n * 2; i ++)
		inv[i] = 1ll * inv[i - 1] * inv[i] % mod;
}
LL C (int a, int b){
	return 1ll * fac[a] * inv[b] % mod * inv[a - b] % mod;
}
int main (){
	//freopen ("R.in", "r", stdin);
	//freopen ("R.out", "w", stdout);
	scanf ("%d", &n);
	prepare ();
	for (int i = 1; i <= n; i ++){
		g[i] = C (i * 2, i);
		g[i] = g[i] * inv[2] % mod;
		for (int j = 1; j < i; j ++){
			g[i] = ((g[i] - 1ll * g[j] * C ((i - j) * 2, i - j) % mod) + mod) % mod;
		}
	}
	f[0][0][0] = 1;
	for (int i = 0; i <= n; i ++){
		for (int j = 0; j <= n; j ++){
			if (i || j){
				for (int k = max (0, i + j - n); k <= i && k <= j; k ++){
					if (i && k)
						f[i][j][k] = (f[i][j][k] + 1ll * f[i - 1][j][k - 1] * (j - (k - 1)) % mod) % mod;
					if (j && k)
						f[i][j][k] = (f[i][j][k] + 1ll * f[i][j - 1][k - 1] * (i - (k - 1)) % mod) % mod;
					if (i)
						f[i][j][k] = (f[i][j][k] + 1ll * f[i - 1][j][k] * (n - (i - 1 + j - k)) % mod) % mod;
					if (j)
						f[i][j][k] = (f[i][j][k] + 1ll * f[i][j - 1][k] * (n - (i + j - 1 - k)) % mod) % mod;
					for (int d = 1; d <= k; d ++)
						f[i][j][k] = ((f[i][j][k] - 1ll * f[i - d][j - d][k - d] * C (n - ((i - d) + (j - d) - (k - d)), d) % mod * fac[d] % mod * g[d] % mod) % mod + mod) % mod;
				}
			}
		}
	}
	printf ("%lld\n", f[n][n][n]);
	return 0;
}

谢谢!

发布了61 篇原创文章 · 获赞 32 · 访问量 8346

猜你喜欢

转载自blog.csdn.net/weixin_43908980/article/details/97480382