0%

50809102 NOIP模拟题

a

给定程序,求程序地期望运行结果

考虑一个长度为 \(n\) 地排列,其逆序对数地期望为 \(\frac{\binom{n}{2}}{2}\),由此,不妨设 \(f_n\) 为长度为 \(n\) 的排列期望运行结果,则有

\[ f_n\ =\ \frac{n(n-1)}{4}+\frac{1}{2^n}\sum_{i=0}^n\binom{n}{i}f_i \] 这时候可以移项得到一个 \(O(n^2)\)\(DP\) 方程,但是我们考虑求出 \(f_n\) 的通项,将组合数拆开有

\[ \frac{f_n}{n!}\ =\ \frac{n(n-1)}{4}\times\frac{1}{n!}+\frac{1}{2^n}\sum_{i=0}^n\frac{1}{(n-i)!}\frac{f_i}{i!} \]

不妨设 \(<f_0,f_1,f_2...>\)\(egf\)\(f\)\(<0,0,2,6...>\)\(egf\)\(g\)

则有

\[ f(x)\ =\ \frac{1}{4}g(x)+e^{\frac{x}{2}}f(\frac{x}{2}) \]

根据 \(EGF\) 性质

\[ x^me^x\ =\ \sum_n[n\ge m]n^{\underline{m}}\frac{x^n}{n!} \]

可以得到 \(g\ =\ x^2e^x\)

盲猜一波如果方程成立,那么解一定为 \(cx^2e^x\) 的形式,解出 \(c=\frac{1}{3}\),则 \(f_n=\frac{n(n-1)}{3}\)

那么答案为

\[ \frac{1}{n}\sum_{i=1}^nf_i=\frac{1}{3n}\times[\frac{n(n+1)(2n+1)}{6}-\frac{n(n+1)}{2}]=\frac{n^2-1}{9} \]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <cstdio>
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstring>
using namespace std;
typedef long long int64;
const int P = 998244353;
int64 qpow(int64 a, int b)
{
int64 ret = 1;
for( ; b; b >>= 1, a = a*a%P) if(b&1) ret = ret*a%P;
return ret;
}
int n;
int main()
{
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
scanf("%d", &n); printf("%lld\n", (n*n-1)*qpow(9, P-2)%P);
return 0;
}

c

给定一颗树,已知 \(s\)\(t\), 求 \(s\)\(t\) 按题意要求游走的最坏情况下的最小花费

首先考虑 \(s\)\(t\) 直接相连的情况,此时最坏的情况一定出现在以 \(t\) 为根,\(s\) 的子树内,先分类讨论,如果节点 \(x\) 为叶子节点,那么它的代价为从 \(s\) 到该节点再到 \(t\) 的最小花费,最小花费由两部分组成,首先是保证可以到达该节点,并在该节点停在直到删边后满足 \(x\)\(t\) 有唯一通路,即 \(x\)\(t\)\(t\) 以外的路径上每个点的子节点数减 \(1\),其次保证从这个节点可以回到 \(s\)节点,那么这一部分代价为 \(dis(x,\ s)\),如果 \(x\) 为非叶节点,那么在这个节点时,最优解一定为先删掉代价最大子节点对应的边,所以这个节点的代价为子节点中的次大代价,同时如果这个节点无次大值,那么可以额外耗费 \(1\) 的花费使它变为叶子

之后考虑 \(s\)\(t\) 不直接相连的情况,注意这是 \(s\) 若一开始就向 \(t\) 的方向随机游走可能情况会变得更差,所以我们最后的最小花费应不超过从 \(s\)\(t\) 方向游走过程中的最坏情况的最优花费,答案具有单调性,考虑二分值为 \(mid\),定义每个节点的代价 \(f\) 为由离着个点最近的 \(s\)\(t\) 的节点出发再走到 \(t\) 的最小花费,判断 \(mid\) 是否合法则模拟从 \(s\)\(t\) 方向游走的过程,设当前枚举的路径上的点为 \(u\),其不在路径上的子节点为 \(v\),游走过程中由于需要满足最坏情况不超过 \(mid\),则需要删除一些边,即枚举过程中删除的边为 \(del\),则当 \(f_v-[u=s]+del>mid\) 时需要删除 \((u,\ v)\) 这条边,前面的减 \([u=s]\) 时由于这个点就是由 \(s\) 向上游走来的,而在 \(f_v\) 中计算了删边的贡献,所以要减去,最后设当前枚举的 \(u\)\(s\)\(t\) 的第 \(i\) 个点,则它有 \(i\) 次删除额外边的机会,依此来判断 \(mid\) 是否合法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <vector>
using namespace std;
inline int read(int f = 1, int x = 0, char ch = ' ')
{
while(!isdigit(ch = getchar())) if(ch == '-') f = -1;
while(isdigit(ch)) x = x*10+ch-'0', ch = getchar();
return f*x;
}
const int N = 1e6+5;
struct Edge
{
int next, to, w;
Edge(int next = 0, int to = 0):next(next), to(to) {};
}edge[N<<1];
int tot, head[N];
void _add(int x, int y) { edge[++tot] = Edge(head[x], y), head[x] = tot; }
void add(int x, int y) { _add(x, y), _add(y, x); }
int n, m, s, t, in[N], d[N], f[N], g[N], fa[N], c[N], ans;
void dfs(int x)
{
for(int i = head[x]; i; i = edge[i].next)
{
int y = edge[i].to; if(y == fa[x]) continue;
fa[y] = x, d[y] = d[x]+1, dfs(y);
}
}
void solve(int x, int topf, int c)
{
if(in[x]) topf = x; int m = 0;
for(int i = head[x]; i; i = edge[i].next) m += edge[i].to != fa[x];
c += m?m-1:0, m = 0;
for(int i = head[x], y; i; i = edge[i].next) if((y = edge[i].to) != fa[x]) solve(y, topf, c);
for(int i = head[x], y; i; i = edge[i].next) if((y = edge[i].to) != fa[x]) g[++m] = f[y];
sort(g+1, g+1+m), m <= 1?f[x] = m+d[x]-d[topf]+c:f[x] = g[m-1];
}
vector<int> val[N];
bool valid(int mid)
{
for(int i = 1, del = 0; i <= m; ++i)
{
vector<int> &w = val[i];
for(int k = 0; k < w.size(); ++k) del += w[k]-(i != 1)+del > mid;
if(del > i) return false;
}
return true;
}
int main()
{
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
n = read(), t = read(), s = read();
for(int i = 1, x, y; i < n; ++i) x = read(), y = read(), add(x, y);
dfs(t);
for(int x = s; x != t; x = fa[x]) in[x] = 1;
for(int i = head[t]; i; i = edge[i].next) solve(edge[i].to, t, 0);
for(int x = s; x != t; x = fa[x])
{
vector<int> &w = val[++m];
for(int i = head[x], y; i; i = edge[i].next)
if(!in[y = edge[i].to]) w.push_back(f[y]);
sort(w.begin(), w.end());
}
int l = f[s], r = n<<1;
while(l <= r)
{
int mid = (l+r)>>1;
if(valid(mid)) ans = mid, r = mid-1;
else l = mid+1;
}
printf("%d\n", f[s]);
return 0;
}