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;
}

謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章