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
*/