T1
【题目描述】
Akari 的学校的校门前生长着一排 n 棵树,从西向东依次编号为 1 ∼ n。相邻两棵树间的距离 都是 1。 Akari 上课的教学楼恰好在树 1 旁,所以每个课间,Akari 都很想走出教室,上树活动。Akari 会依次经过 m 棵树,从树 1 一路向东跳到树 n。临近上课时,Akari 会再次上树,经过 m 棵树从树 n 一路向西跳到树 1 ,准备上课。由于 Akari 睡眠很充足,Akari 每次跳跃至少会移动 k 的距离, 因此 Akari 在上树前需要合理规划她的跳跃路线。我们称每次上树过程中 Akari 跳过的全部 m 棵 树(包含树 1 和树 n)的集合为一条树上路径。 Akari 喜欢按不同的顺序观察各种树木,因此她每次上树时选择的树上路径不会与之前选择过 的重复。这意味着,Akari 不会选择之前的课间选过的树上路径,且在从树 n 跳回树 1 时,也不会 沿这次跳到树 n 的树上路径原路返回。 如果一次课间开始时,Akari 找不到符合条件的树上路径,那么她从此会放弃上树活动,开始 专心学习。如果一次课间即将即将结束时,Akari 还在树 n 且找不到符合条件的树上路径回到树 1, 她就会十分沮丧,选择逃课。 请你帮助 Akari 判断,她是否会在某个课间选择逃课。
【题解】
显然原问题可以转化为树上路径的数目。
由于每一次需要至少往后跳k棵树,若把每一个落脚的树看做一个块的开头,则原问题可转化为将长度为n-1(第n棵树单独占一个块)的序列划分为m-1(同前)个块,且每个块的大小至少为k的方案数。
其实仔细想想,还可以进一步转化:将n-1个相同小球放入m-1个不同盒子里,每一个盒子里的球数不少于k。这就是一个十分经典的组合问题了。答案为
C
n
−
1
−
(
m
−
1
)
k
+
m
−
1
−
1
m
−
1
C_{n-1-(m-1)k+m-1-1}^{m-1}
Cn−1−(m−1)k+m−1−1m−1。
这么简单的问题居然想了1.5h
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod = 2;
inline ll getint()
{
ll ret = 0, flg = 1; char c;
while((c = getchar()) < '0' || c > '9')
if(c == '-') flg = -1;
while(c >= '0' && c <= '9')
ret = ret * 10 + c - '0', c = getchar();
return ret * flg;
}
inline ll c(int a, int b)
{
if(a < b) return 0;
return 1;
}
inline ll C(ll a, ll b)
{
if(a < b) return 0;
if(a == b || b == 0) return 1;
return C(a / mod, b / mod) * c(a % mod, b % mod) % mod;
}
int main()
{
freopen("phantasm.in", "r", stdin);
freopen("phantasm.out","w",stdout);
int T;
ll n, m, k;
scanf("%d", &T);
while(T--)
{
n = getint(), m = getint(), k = getint(), --n, --m;
ll N = n - m * k, M = m;
puts(C(N + M - 1, M - 1) ? "Yes" : "No");
}
}
T2
【题目描述】
小 A 的城市里有 n 座工厂,编号分别为 1 ∼ n。工厂间连有 n − 1 条双向管道,形成一个无向 连通图,其中每条管道都有一定的长度,连接在两座不同的工厂间。 每座工厂都装有废水处理设施,工厂 i 的蓄水量记为 ci。由于工厂规模有限,工厂产生的废水 必须经由管道输送到另一座工厂进行处理。 工厂 u 将废水输送到工厂 v 处理时,所需的运输成本等于无向图中 u, v 间最短路径的长度,并 且会产生 cu − cv 的额外成本(可能为负)。总成本等于运输成本与额外成本的和。 为了降低污染,在接下来的 q 天内,每一天只有一座工厂会产生废水。你需要确定这座工厂将 废水输送到哪一座工厂进行处理,可使得总成本最小。由于选择可能不唯一,你只需输出最小的总 成本。
【题解】
对于一条路径<u,v>上的任意一点w,都有:
c
o
s
t
(
u
,
w
)
+
c
o
s
t
(
w
,
v
)
=
d
i
s
(
u
,
w
)
+
c
[
u
]
−
c
[
w
]
+
d
i
s
(
w
,
v
)
+
c
[
w
]
−
c
[
v
]
=
d
i
s
(
u
,
v
)
+
c
[
u
]
−
c
[
v
]
\begin{aligned} &cost(u,w)+cost(w,v)\\ =&dis(u,w)+c[u]-c[w]+dis(w,v)+c[w]-c[v]\\ =&dis(u,v)+c[u]-c[v]\\ \end{aligned}
==cost(u,w)+cost(w,v)dis(u,w)+c[u]−c[w]+dis(w,v)+c[w]−c[v]dis(u,v)+c[u]−c[v]
所以要想找一个点u的最优解,就可以直接从它的邻接点转移过来。
对于一个节点,它的最优目的地位置只有两种情况:在子树内;在子树外。这不是废话吗
对于子树内的情况,根据上面的分析,一发树形dp即可搞定。
对于子树外的情况,根据上面的结论,不难发现:若一个点u父亲的最优决策点不在它上,则答案为父亲的最优选择+cost(u,fa[u]),否则为父亲的次优选择+cost(u,fa[u])。
两次dfs,维护子树内最小值、次小值、子树外最小值即可。
#include<bits/stdc++.h>
using namespace std;
const int mn = 200005;
vector<int> g[mn], v[mn];
int w[mn], f[mn], d[mn], sec[mn];
bool l[mn];
inline int getint()
{
int ret = 0, flg = 1; char c;
while((c = getchar()) < '0' || c > '9')
if(c == '-') flg = -1;
while(c >= '0' && c <= '9')
ret = ret * 10 + c - '0', c = getchar();
return ret * flg;
}
void dfs1(int s, int fa)
{
int m = g[s].size();
for(int i = 0; i < m; i++)
{
int t = g[s][i];
if(t != fa)
{
dfs1(t, s);
if(f[s] > min(0, f[t]) + v[s][i] + w[s] - w[t])
sec[s] = min(sec[s], f[s]), f[s] = min(0, f[t]) + v[s][i] + w[s] - w[t];
else if(sec[s] > min(0, f[t]) + v[s][i] + w[s] - w[t])
sec[s] = min(0, f[t]) + v[s][i] + w[s] - w[t];
}
}
}
void dfs2(int s, int fa, int las)
{
if(fa)
{
if(f[fa] == min(0, f[s]) + las + w[fa] - w[s])
d[s] = min(d[s], min(0, sec[fa]) + las + w[s] - w[fa]);
else
d[s] = min(d[s], min(0, f[fa]) + las + w[s] - w[fa]);
d[s] = min(d[s], min(0, d[fa]) + las + w[s] - w[fa]);
}
int m = g[s].size();
for(int i = 0; i < m; i++)
{
int t = g[s][i];
if(t != fa)
dfs2(t, s, v[s][i]);
}
}
int main()
{
int n, m, a, b, c;
n = getint();
for(int i = 1; i <= n; i++)
w[i] = getint(), f[i] = d[i] = sec[i] = 1 << 30;
for(int i = 1; i < n; i++)
a = getint(), b = getint(), c = getint(),
g[a].push_back(b), g[b].push_back(a),
v[a].push_back(c), v[b].push_back(c);
dfs1(1, 0), dfs2(1, 0, 0), m = getint();
while(m--)
a = getint(), printf("%d\n", min(f[a], d[a]));
}
T3
【题目描述】
简化版题意:
随机生成一个m+1个数的数列,第一个数为0, 生成第i个数时,在前i−1个数中等概率选择一个数k, 则第i 个数为k+1。数字i有一个对应的权值ai,求数列权值和的期望。
【题解】
设f[i][j]为前i个数中出现j的概率,则
f
[
i
]
[
j
]
=
∑
k
=
1
i
−
1
f
[
k
]
[
j
−
1
]
i
−
1
f[i][j]=\sum_{k=1}^{i-1}\frac{f[k][j-1]}{i-1}
f[i][j]=k=1∑i−1i−1f[k][j−1]
所以长度为i的序列权值的期望为:
v
a
l
[
i
]
=
∑
j
=
1
m
f
[
i
]
[
j
]
val[i]=\sum_{j=1}^{m}f[i][j]
val[i]=j=1∑mf[i][j]
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mn = 100005, mm = 25, mod = 998244353;
ll a[mn], inv[mm], f[mm][mm], val[mm];
inline ll ksm(ll a, int b)
{
ll ret = 1;
while(b)
{
if(b & 1)
(ret *= a) %= mod;
(a *= a) %= mod, b >>= 1;
}
return ret;
}
int main()
{
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
for(int i = 1; i <= m; i++)
inv[i] = ksm(i, mod - 2);
f[1][0] = 1;
for(int i = 2; i <= m + 1; i++)
for(int j = 1; j < i; j++)
{
for(int k = 1; k < i; k++)
(f[i][j] += f[k][j-1] * inv[i-1] % mod) %= mod;
(val[i] += f[i][j] * a[j] % mod) %= mod;
}
ll ans = 0;
for(int i = 2; i <= m + 1; i++)
(ans += val[i]) %= mod;
printf("%lld\n", ans);
}