1. 程式人生 > 實用技巧 >11.13 考試總結

11.13 考試總結

wdnmd,真就都不會做嗷。

T1 graph

Desprition

cwbc 想要隨機生成一個特殊的 \(n\) 個節點的有向圖,每個節點有且僅有一條出 邊,已知生成該圖時,每個點的出邊指向每個點(包括自己)的概率是相同的, 現 在要你求出該有向圖的弱連通分量的期望個數。 你只需要給出該期望值乘以 \(n^n\) 並對 998244353 取模的結果即可。

弱連通分量:在一張有向圖中,將所有有向邊變為無向邊後,每個連通塊稱為個弱連通分量 (\(n\leq 10^7\)).

solution

神仙計數題。

首先我們會發現生成的圖是個基環樹森林,關於這個圖有一個性質,每個弱聯通分量有且僅有一個環。

於是我們求出環的數量就可以得到弱聯通分量的數量。

考慮列舉一下每個環的大小。

對於點數為 \(i\) 的環,我們從 \(n\) 個點中選出 \(i\) 個點來組成這個環共有 \(C_{n}^{i}\) 中選法。

對於這 \(i\) 個點,要向讓他們組成一個為 大小為 \(i\) 的環,那麼第一個點可以 和 剩下的 \(i-1\) 個點相連。

第二個點和剩下的 \(n-2\) 個點相連 .....以此類推第 \(n\) 個點只能與第一個點相連。

也就是說每個點不能和之前已經連過的點在連邊,因此方案數為 \((i-1)!\).

對於剩下的 \(n-i\) 的點,顯然這些點可以對所有的點相連,方案數位為 \(n^{n-i}\)

綜上答案為: \(ans = \displaystyle\sum_{i=1}^{n}C_{n}^{i}\times (i-1)!\times n^{n-i}\)

在除以圖的數量 \(n^n\) 就可以得到最後的期望。

可以線性求逆元,將時間複雜度降為 \(O(n)\)

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define int long long
const int p = 998244353;
const int N = 1e7+10;
int n,ans,base[N],jz[N],inv[N];
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
int ksm(int a,int b)
{
	int res = 1;
	for(; b; b >>= 1)
	{
		if(b & 1) res = res * a % p;
		a = a * a % p;
	}
	return res;
}
void YYCH()
{
	jz[0] = inv[0] = 1; base[0] = 1;
	for(int i = 1; i <= n; i++) jz[i] = jz[i-1] * i % p;
	inv[n] = ksm(jz[n],p-2);
	for(int i = n-1; i >= 1; i--) inv[i] = inv[i+1] * (i+1) % p;
	for(int i = 1; i <= n; i++) base[i] = base[i-1] * n % p;
}
int C(int n,int m)
{
	return jz[n] * inv[m] % p * inv[n-m] % p;
}
signed main()
{
	freopen("graph.in","r",stdin);
	freopen("graph.out","w",stdout);
	n = read(); YYCH();
	for(int i = 1; i <= n; i++)
	{
		ans = (ans + C(n,i) * jz[i-1] % p * base[n-i] % p);
	}
	printf("%lld\n",ans);
	fclose(stdin); fclose(stdout);
	return 0;
}

T2 jewelry

Desprition

給你一個 \(n\) 個節點的樹,讓你求出每個大小為 \(k\) 的聯通塊中點編號中位數的最大值。

\(n,k\leq 2000\) 保證 \(k\) 為奇數。

solution

考試的時候打了個鏈的暴力就走了,正解一點思路都沒有。

考慮一下二分答案 ,設答案為 \(mid\),我們可以求出每個大小為 \(k\) 的聯通塊中比 \(mid\) 大的個數 \(cnt\)

如果 \(cnt >= \lfloor {k\over 2} \rfloor\) 說明我們的答案較小,反之較大。

然後問題就轉換為了怎麼求出大小為 \(k\) 的聯通塊中比 \(mid\) 大的數的個數。

\(f[x][i]\) 表示在以 \(x\) 為根的子樹中,選 \(x\) 且大小為 \(i\) 的聯通塊中比 \(mid\) 大的數最多有多少個。

轉移則有 $f[x][i+j] = max(f[x][i+j],f[x][i] + f[to][j]) $

初始化 \(f[x][0] = 0, f[x][1] = (x >= mid)\).

注意列舉 \(i\) 的時候要從 \(1\) 開始迴圈,因為 \(x\) 這個點必須要選。

直接dp的複雜度是 \(O(n^3)\) 的,考慮每個點只用列舉到 \(siz[x]\) 就行,這樣複雜度就降為 \(O(n^2)\)

總複雜度 \(O(n^2logn)\).

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 3010;
int n,k,tot,L,R,ans,u,v;
int head[N],siz[N],f[N][N];
struct node
{
	int to,net;
}e[100010];
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
void add(int x,int y)
{
	e[++tot].to = y;
	e[tot].net = head[x];
	head[x] = tot;
}
void dfs(int x,int fa)
{
	siz[x] = 1;
	for(int i = head[x]; i; i = e[i].net)
	{
		int to = e[i].to;
		if(to == fa) continue;
		dfs(to,x);
		for(int j = min(k,siz[x]); j >= 1; j--)//每個點只能選一次,所以倒序迴圈
		{
			for(int u = 0; u <= siz[to]; u++)
			{
				f[x][j + u] = max(f[x][j + u], f[x][j] + f[to][u]);
			}
		}
//		cout<<x<<" "<<f[x][k]<<endl;
		siz[x] += siz[to];
	} 
}
bool judge(int mid)
{
	memset(f,0,sizeof(f));
	for(int i = 1; i <= n; i++) f[i][1] = i >= mid ? 1 : 0; 
	dfs(1,1);
	for(int i = 1; i <= n; i++)
	{
		if(f[i][k] >= k/2+1) return 1;
	}
	return 0;
}
int main()
{
	freopen("jewelry.in","r",stdin);
	freopen("jewelry.out","w",stdout);
	n = read(); k = read();
	for(int i = 1; i <= n-1; i++)
	{
		u = read(); v = read();
		add(u,v); add(v,u);
	}
	L = 1, R = n;
	while(L <= R)
	{
		int mid = (L + R)>>1;
		if(judge(mid)) 
		{
			L = mid + 1;
			ans = mid;
		}
		else R = mid - 1;
	}
	printf("%d\n",ans);
	fclose(stdin); fclose(stdout);
	return 0;
}

T3 labyrinth

Desprition

有一個 \(n*m\) 的網格,起點在 第 \(sx\)\(sx\) 列,終點在 第 \(tx\)\(tx\) 列,每個格子上的數字為 \(a[i][j]\).每次可以跳到同一行或者同一列的某個格子,花費為起跳點和終點之間所有格子上數字的最小值,請你求出從起點到終點的最小總花費。

\(n,m\leq 8000,n*m\leq 100000,a[i][j]\leq 1000\).

solution

只打了個 \(O(n^2m+m^2)\) 和所有 \(a[i][j]\) 相等的暴力。

正解好像要建虛點,不會爪八了。

T4 joseph

Desprition

約瑟夫遊戲的規則是這樣的:\(n\) 個人圍成一圈,從 \(1\) 號開始依次報數,當報 到 \(m\) 時, 報 \(1、2、...、m-1\) 的人出局,下一個人接著從 \(1\) 開始報,保證 \((n-1)\)\((m-1)\) 的倍數。最後剩的一個人獲勝。 YJC 很想贏得遊戲,但他太笨了,他想讓你幫他算出自己應該站在哪個位置 上。

\(n,m\leq 2^{63}-1,保證 (n-1) 是 (m-1) 的倍數\)

solution

正解好像是 \(dp\) ,但我不會寫,我直接找規律做的QAQ。

假設當前有 \(n\) 個人,直到第 \(n\) 個人報完停止一輪。

那麼下一輪的報數順序可以寫成 \(n,n-1,n-2...n-n\bmod m,m,2m,3m,4m...{n\over m}m\).

我們可以繼續往下遞迴,考慮怎麼有下一層必勝的位置 \(tmp\),推出這一層必勝的位置。

\(tmp \leq n\mod m\) ,顯然只會是上一輪後面的那幾個數,所以直接返回 \(n-tmp+1\)

反之則在上一輪的位置為 \(km\) 的位置,其中 $k = tmp- n \bmod m $ .

自己可以畫畫圖手動理解 一下。

遞迴邊界 \(n=1, return 1\)。 複雜度 O(玄學)。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define int long long
int n,m;
inline int read()
{
	int s = 0,w = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * w;
}
int slove(int n)
{
	if(n == 1) return 1;
	int tmp = slove(n/m+n%m);
	if(tmp <= n % m) return n - tmp + 1;
	else return m * (tmp - n % m); 
}
signed main()
{
//	freopen("joseph.in","r",stdin);
//	freopen("joseph.out","w",stdout);.
	n = read(); m = read();
	printf("%lld\n",slove(n));
	fclose(stdin); fclose(stdout);
	return 0;
}
/*
461168601842738905 3
*/