1113考试总结
1113考试总结
T1
题目大意:
随机生成一个特殊的 n 个节点的有向图,每个节点有且仅有一条出边,已知生成该图时,每个点的出边指向每个点(包括自己)的概率是相同的, 现在要你求出该有向图的 弱连通分量的期望个数。你只需要给出该期望值以 乘以 \(n ^ n\) 对 并对 \(998244353\) 取模的结果即可。 \(n <= 1e7\)
弱连通分量 : 在一张有向图中,将所有有向边变为无向边后, 每一个连通块称为一个弱连通分量。
组合数, 期望.
这道题已经降低难度了.因为题目说对期望值乘上\(n ^ n\).可以发现, 一共有\(n ^ n\)种不同的无向图, 那么每一种图出现的概率就是\(\frac{1}{n ^ n}\).我们知道期望等于权值乘概率, 概率消掉了, 只剩权值.所以题目转化为求弱连通分量的总个数.
我们可以发现每一个弱联通分量都是一个基环内向树,也就是说一个弱连通分量有且仅有一个环(无向), 然后问题又转换成了求环的总个数.
\(\displaystyle \sum_{i = 1}^{n} C_{n}^{i}*(i - 1)!*n^{(n - i)}\).
解释一下:\(i\)为当前环的大小,也就是有几个点的环.然后\(i\)个点的环有\((i - 1)!\)种排列方式,因为第一个点可以向外伸出\(i - 1\)条边,第二个点\(i - 2\)条边.....也就是\((i - 1) * (i - 2) * ... * 1 = (i - 1)!\).然后剩下\(n - i\)个点, 每个点可以向外连\(n\)条.
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e7 + 5, mod = 998244353;
int n, ans;
int fac[N], inv[N], pow_n[N];
void make_pre() {
fac[0] = fac[1] = inv[0] = inv[1] = pow_n[0] = 1;
for(int i = 1;i <= N - 5; i++) pow_n[i] = 1ll * pow_n[i - 1] * n % mod;
for(int i = 2;i <= N - 5; i++) fac[i] = 1ll * fac[i - 1] * i % mod;
for(int i = 2;i <= N - 5; i++) inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
for(int i = 2;i <= N - 5; i++) inv[i] = 1ll * inv[i - 1] * inv[i] % mod;
}
int C(int x, int y) {
if(x < y) return 0;
return 1ll * fac[x] * inv[y] % mod * inv[x - y] % mod;
}
int ksm(int x, int y) {
int res = 1;
while(y) {
if(y & 1) res = 1ll * res * x % mod;
x = 1ll * x * x % mod; y >>= 1;
}
return res;
}
int main() {
n = read(); make_pre();
for(int i = 1;i <= n; i++) ans = (ans + 1ll * C(n, i) * fac[i - 1] % mod * pow_n[n - i] % mod) % mod;
printf("%d", ans);
return 0;
}
T2
题目大意:
有n 个珠宝,编号为 1..n,这个 n 个珠宝被 n-1 条绳子连接了起来,恰好连接成了一棵树的形态。要求恰好留下 k 个珠宝。为了方便取下这 k 个珠宝,同时不使
这 k 个珠宝失散,他要求留下的 k 个珠宝必须恰好是树形态中的一个连通块。同时使得留下的k 个珠宝的编号的中位数最大。为了方便定义中位数,这里保证 k 为奇数。\(n <= 3000\)
二分 + 树上背包.
我们二分中位数\(mid\).
\(f[x][i]\)表示以\(x\)为根的子树内, 节点数为\(i\)的联通块内, 编号大于等于\(mid\)的个数.如果说\(max(f[x][k]) >= k / 2 + 1\)说明当前的\(mid\)合法, 增大\(l\),否则减小\(r\).
那么怎么计算\(f\)数组呢?
设\(y\)为\(x\)的一个子节点, 我们现在要合并\(y\)的子树与已经与\(x\)合并过的那些子树的答案.令\(h[i + j] = f[x][i] + f[y][j]\), 然后\(f[x][i] = h[i]\).统计完啦!很好理解.
此时计算\(f\)数组的复杂度是\(O(n ^ 3)\)的, 我们可以使枚举上限变成\(siz[x]\)来降低复杂度.题解说可以降低为\(O(n ^ 2)\)我也不知道为啥.
总复杂度\(O(n ^ 2log\ n)\)
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 3005;
int n, k, cnt, ans, mid, tmp;
int f[N][N], h[N << 1], siz[N], head[N];
struct edge { int to, nxt; } e[N << 1];
void add(int x, int y) {
e[++ cnt].nxt = head[x]; head[x] = cnt; e[cnt].to = y;
}
void dfs(int x, int fa) {
f[x][1] = (x >= mid); siz[x] = 1;
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to; if(y == fa) continue;
dfs(y, x);
for(int j = 1;j <= siz[x] + siz[y]; j++) h[j] = 0;
for(int j = 1;j <= siz[x]; j++)
for(int k = 0;k <= siz[y]; k++) h[j + k] = max(h[j + k], f[x][j] + f[y][k]);
for(int j = 1;j <= siz[x] + siz[y]; j++) f[x][j] = h[j];
siz[x] += siz[y];
}
if(siz[x] >= k) tmp = max(tmp, f[x][k]);
}
int main() {
n = read(); k = read();
for(int i = 1, x, y;i < n; i++)
x = read(), y = read(), add(x, y), add(y, x);
int l = 1, r = n;
while(l < r) {
mid = (l + r) >> 1; tmp = 0; dfs(1, 0);
if(tmp >= k / 2 + 1) l = mid;
else r = mid - 1;
}
printf("%d", l);
return 0;
}
T3
题目大意:
迷宫是一个 n 行m 列的网格,起点在第 sx 行第sy 列,终点在第 tx 行第 ty 列。迷宫里的每个格子上有一个数字,第 i 行第 j列的数字记为 a(i,j)。
从起点开始,每次可以跳到同一行或者同一列的某个格子,但是这一跳会产生一定的花费,花费的大小为起跳点和落地点之间所有格子(包含这两个格子)上的数字的最小值。求出从起点到终点的最小总花费。\(n * m <= 100000\).
建图 , 最短路.md建图老恶心了
我们如果让每个格子和自己同行同列的所有格子都连边的话, 需要建\(n * m * (n + m)\)条边,这肯定是建不出来的.
考虑另一种建图方式, 方个图先:
方格是原图上的方格, 红蓝点都是虚点.我们按照上图的建图方式, 都是有向边,红边和蓝边边权为0,棕色边边权为跨过格子上的数字.从方格里开始走,必定先走到红点,再走到蓝点,再走到另一个格子里.就相当于是总左往右找了一条路径,使一个格子到另一个格子经过他们之间的最小值.所以从右往左,从上往下,从下往上都是同理,建完图跑一边dij就行了.
这么建图点大概有\(4nm\)个,边大概有\(20nm\),是可以建出来的.
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e6 + 5;
const long long inf = 1e18;
int n, m, d, b, s, t, nn, cnt, tag;
int a[N], f[N], g[N], vis[N], head[N];
long long dis[N];
struct edge { int to, nxt, val; } e[7000005 * 2];
int get_id(int x, int y) { return (x - 1) * m + y; }
void add(int x, int y, int z) {
e[++ cnt].nxt = head[x]; head[x] = cnt; e[cnt].to = y; e[cnt].val = z;
}
void run_dij() {
priority_queue <pair<long long, int>, vector<pair<long long, int> >, greater<pair<long long, int> > > q;
for(int i = 1;i <= nn; i++) dis[i] = inf;
dis[s] = 0; q.push(make_pair(dis[s], s));
while(!q.empty()) {
int x = q.top().second; q.pop();
if(vis[x]) continue; vis[x] = 1;
for(int i = head[x]; i ; i = e[i].nxt) {
int y = e[i].to;
if(dis[y] > dis[x] + e[i].val)
dis[y] = dis[x] + e[i].val, q.push(make_pair(dis[y], y));
}
}
}
void work() {
nn = get_id(n, m);
for(int i = 1;i <= n; i++) {
for(int j = 1;j <= m; j++) {
int id = get_id(i, j);
f[j] = ++ nn; g[j] = ++ nn;
add(id, f[j], 0); add(g[j], id, 0); add(f[j], g[j], a[id]);
}
for(int j = 1;j < m; j++) add(f[j], f[j + 1], 0), add(g[j], g[j + 1], 0);
for(int j = 1;j <= m; j++) {
int id = get_id(i, j);
f[j] = ++ nn; g[j] = ++ nn;
add(id, f[j], 0); add(g[j], id, 0); add(f[j], g[j], a[id]);
}
for(int j = 1;j < m; j++) add(f[j + 1], f[j], 0), add(g[j + 1], g[j], 0);
}
for(int i = 1;i <= m; i++) {
for(int j = 1;j <= n; j++) {
int id = get_id(j, i);
f[j] = ++ nn; g[j] = ++ nn;
add(id, f[j], 0); add(g[j], id, 0); add(f[j], g[j], a[id]);
}
for(int j = 1;j < n; j++) add(f[j], f[j + 1], 0), add(g[j], g[j + 1], 0);
for(int j = 1;j <= n; j++) {
int id = get_id(j, i);
f[j] = ++ nn; g[j] = ++ nn;
add(id, f[j], 0); add(g[j], id, 0); add(f[j], g[j], a[id]);
}
for(int j = 1;j < n; j++) add(f[j + 1], f[j], 0), add(g[j + 1], g[j], 0);
}
run_dij(); printf("%lld", dis[t]);
}
int main() {
n = read(); m = read();
d = read(); b = read(); s = get_id(d, b);
d = read(); b = read(); t = get_id(d, b);
for(int i = 1;i <= n; i++)
for(int j = 1;j <= m; j++) a[get_id(i, j)] = read();
work();
return 0;
}
T4
题目大意:
约瑟夫游戏的规则是这样的:n 个人围成一圈,从 1 号开始依次报数,当报到 m 时, 报 1、2、...、m-1 的人出局,下一个人接着从 1 开始报,保证(n-1)是(m-1)的倍数。最后剩的一个人获胜。求最后剩下的那一个人的位置.\(n <= 2 ^ {64} - 1\).
递归.
设当前有\(n\) 个人,\(m\)次.那么第一轮进行完后将会剩下\(n / m + n \% m\)个人.这里的轮数是指数不超过\(n\)个人直到不能再数为止.
比如: n = 13, m = 4, 第一轮数完就剩4, 8, 12, 13. 然后我们将\(n \% m\)提前, 13, 4, 8, 12
设\(solve(n)\)表示\(n\)个人围成一圈时的答案.设\(tmp = solve(n / m +n \% m)\).
如果说\(tmp <= n \% m\)说明最后剩下的那个人一定是在本轮里最后剩下的那几个人里, 所以最后剩下的那个人在当前轮的位置就是\(n - tmp + 1\).
如果说\(tmp > n \% m\)说明最后剩下的那个人在\(n / m\)那几个人里, 所以在当前轮的位置就是\((tmp - n \% m) * m\).
#include <bits/stdc++.h>
using namespace std;
inline long long read() {
long long s = 0, f = 1; char ch;
while(!isdigit(ch = getchar())) (ch == '-') && (f = -f);
for(s = ch ^ 48;isdigit(ch = getchar()); s = (s << 1) + (s << 3) + (ch ^ 48));
return s * f;
}
const int N = 1e6 + 5;
long long n, m;
long long solve(long long n) {
if(n == 1) return 1;
long long tmp = solve(n / m + n % m);
if(tmp <= n % m) return n - tmp + 1;
else return m * (tmp - n % m);
}
int main() {
n = read(); m = read();
cout << solve(n);
return 0;
}