wdnmd,真就都不会做嗷。
T1 graph
Desprition
cwbc 想要随机生成一个特殊的 (n) 个节点的有向图,每个节点有且仅有一条出 边,已知生成该图时,每个点的出边指向每个点(包括自己)的概率是相同的, 现 在要你求出该有向图的弱连通分量的期望个数。 你只需要给出该期望值乘以 (n^n) 并对 998244353 取模的结果即可。
弱连通分量:在一张有向图中,将所有有向边变为无向边后,每一个连通块称为一个弱连通分量 ((nleq 10^7)).
solution
神仙计数题。
首先我们会发现生成的图是个基环树森林,关于这个图有一个性质,每个弱联通分量有且仅有一个环。
于是我们求出环的数量就可以得到弱联通分量的数量。
考虑枚举一下每个环的大小。
对于点数为 (i) 的环,我们从 (n) 个点中选出 (i) 个点来组成这个环共有 (C_{n}^{i}) 中选法。
对于这 (i) 个点,要向让他们组成一个为 大小为 (i) 的环,那么第一个点可以 和 剩下的 (i-1) 个点相连。
第二个点和剩下的 (n-2) 个点相连 .....以此类推第 (n) 个点只能与第一个点相连。
也就是说每个点不能和之前已经连过的点在连边,因此方案数为 ((i-1)!).
对于剩下的 (n-i) 的点,显然这些点可以对所有的点相连,方案数位为 (n^{n-i})
综上答案为: (ans = displaystylesum_{i=1}^{n}C_{n}^{i} imes (i-1)! imes 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
",ans);
fclose(stdin); fclose(stdout);
return 0;
}
T2 jewelry
Desprition
给你一个 (n) 个节点的树,让你求出每个大小为 (k) 的联通块中点编号中位数的最大值。
(n,kleq 2000) 保证 (k) 为奇数。
solution
考试的时候打了个链的暴力就走了,正解一点思路都没有。
考虑一下二分答案 ,设答案为 (mid),我们可以求出每个大小为 (k) 的联通块中比 (mid) 大的个数 (cnt)。
如果 (cnt >= lfloor {kover 2} floor) 说明我们的答案较小,反之较大。
然后问题就转换为了怎么求出大小为 (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
",ans);
fclose(stdin); fclose(stdout);
return 0;
}
T3 labyrinth
Desprition
有一个 (n*m) 的网格,起点在 第 (sx) 行 (sx) 列,终点在 第 (tx) 行 (tx) 列,每个格子上的数字为 (a[i][j]).每次可以跳到同一行或者同一列的某个格子,花费为起跳点和终点之间所有格子上数字的最小值,请你求出从起点到终点的最小总花费。
(n,mleq 8000,n*mleq 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,mleq 2^{63}-1,保证 (n-1) 是 (m-1) 的倍数)。
solution
正解好像是 (dp) ,但我不会写,我直接找规律做的QAQ。
假设当前有 (n) 个人,直到第 (n) 个人报完停止一轮。
那么下一轮的报数顺序可以写成 (n,n-1,n-2...n-nmod m,m,2m,3m,4m...{nover m}m).
我们可以继续往下递归,考虑怎么有下一层必胜的位置 (tmp),推出这一层必胜的位置。
若 (tmp leq nmod m) ,显然只会是上一轮后面的那几个数,所以直接返回 (n-tmp+1)
反之则在上一轮的位置为 (km) 的位置,其中 $k = tmp- n mod 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
",slove(n));
fclose(stdin); fclose(stdout);
return 0;
}
/*
461168601842738905 3
*/