USACO 2008 Jan G
最小点覆盖板子 3种情况讨论
P3574 推式子变形贪心
暴力推dp方程,对式子分析,贪心考虑先后顺序的影响,化简式子排序
Tree
裸换根dp+逆元
CF1101D GCD Counting
首先对题目数据分析,
a
i
a_i
ai最多包含7个不同的质因子,若一条路径
x
−
>
y
x->y
x−>y的
g
c
d
(
x
,
y
)
>
1
gcd(x,y)>1
gcd(x,y)>1,那么路径上的点至少共同包含一个质因子
d p i , j dp_{i,j} dpi,j表示以 i i i为根的子树中,由 i i i到子树中的某个点 j j j的路径中,经过的点都包含 a i a_i ai中第 j j j大的质因子,这种路径的最长长度
状态设计好后就很好转移了,预处理一下每个点权值的质因子有哪些
CF735E Ostap and Tree
最初的想法是
d
p
u
,
j
dp_{u,j}
dpu,j表示为
u
u
u为根节点,离
u
u
u最近的染色点距离为
j
j
j的合法染色方案的有多少种。这种就时合法子树去转移,所以少了一些情况。
对于一些不合法的以
v
1
v1
v1为根子树,可能它树内的不合法点会因为其它子树
v
2
v2
v2中的合法点,在合并到
u
u
u后合法
为此我们既要知道离
u
u
u最近的染色点距离为
j
j
j时的方案数,又要知道离
u
u
u最远的不合法点的距离为
k
k
k时的方案数。然后加法乘法原理合并即可。
1.合法树
u
u
u与合法子树
v
v
v,状态取min
2.合法树
u
u
u与非合法子树
v
v
v,看非合法点与染色点距离是否
<
=
k
<=k
<=k,再转移到
u
u
u的合法状态或非合法状态
3.非合法树
u
u
u与非合法子树
v
v
v ,状态取max
CF1153D Serval and Rooted Tree
将题目抽象为排名,
d
p
u
=
x
dp_{u}=x
dpu=x表示节点u最大为第x大,叶子结点dp[u]=1
1.如果当前节点u操作为min,
u
u
u有一些儿子
v
1
,
v
2
,
v
3
.
.
.
.
v
m
v_1,v_2,v_3....v_m
v1,v2,v3....vm,那么每个儿子的最大排名都会对u产生影响,
d
p
u
=
∑
i
=
0
m
d
p
v
i
dp_{u}=\sum_{i=0}^{m} {dp_{v_i}}
dpu=∑i=0mdpvi
2.如果当前节点
u
u
u操作为max,只有最大的儿子对u产生影响,其它儿子为根的子树内的数字没有影响,随意安排叶子结点,
d
p
u
=
m
i
n
(
d
p
v
1
,
d
p
v
2
.
.
.
d
p
v
m
)
dp_u=min(dp_{v_1},dp_{v_2}...dp_{v_m})
dpu=min(dpv1,dpv2...dpvm)
最终答案为
k
−
d
p
1
+
1
k-dp_1+1
k−dp1+1
CF1097G Vladislav and a Great Legend
首先对于
f
(
S
)
k
f(S)^k
f(S)k,化为第二类斯特林数形式:
∑
S
∑
i
=
0
k
S
2
(
k
,
i
)
i
!
(
f
(
S
)
i
)
\sum_{S}\sum_{i=0}^{k} {S_2(k,i) i!{f(S) \choose i}}
S∑i=0∑kS2(k,i)i!(if(S))
对于
∑
i
=
0
k
S
2
(
k
,
i
)
i
!
\sum_{i=0}^{k} {S_2(k,i)i!}
∑i=0kS2(k,i)i! 是不变的,所以式子变为:
∑
i
=
0
k
S
2
(
k
,
i
)
i
!
×
∑
S
∑
i
=
0
k
(
f
(
S
)
i
)
\sum_{i=0}^{k} {S_2(k,i)i!} \times \sum_{S}\sum_{i=0}^{k} {f(S) \choose i}
i=0∑kS2(k,i)i!×S∑i=0∑k(if(S))
对第二部分加法结合律
∑
i
=
0
k
S
2
(
k
,
i
)
i
!
×
∑
i
=
0
k
∑
S
(
f
(
S
)
i
)
\sum_{i=0}^{k} {S_2(k,i)i!} \times \sum_{i=0}^{k}\sum_{S} {f(S) \choose i}
i=0∑kS2(k,i)i!×i=0∑kS∑(if(S))
∑
i
=
0
k
S
2
(
k
,
i
)
i
!
∑
S
(
f
(
S
)
i
)
\sum_{i=0}^{k} {S_2(k,i)i!} \sum_{S} {f(S) \choose i}
i=0∑kS2(k,i)i!S∑(if(S))
前一部分很好求,那么对于第二部分,考虑它的意义:因为
f
(
S
)
f(S)
f(S)是集合
S
S
S形成虚树的边数,所以可以理解为选
i
i
i条边。
考虑dp做法,因为是虚树,假设当前以
u
u
u为根,点集中包括两个以上儿子,那么u这个点就会自动加入进去,这就是另一种点集的情况。
定义
d
p
u
,
j
dp_{u,j}
dpu,j表示当前根节点为
u
u
u,
u
u
u和它的父亲连边,选了
j
j
j条边方案有多少。这种边转移边更新答案,然后再合并完儿子后,主动处理
u
u
u与
f
a
[
u
]
fa[u]
fa[u]连边是否选的情况
CF835F Roads in the Kingdom
CF592D Super M
首先一定易证一定从某个关键点出发,然后从这个关键点向外延伸路径去遍历其它关键点,观察到延伸的路径只有一条路径不用回来,只用走一遍,其它路径都要过去再回来,为使路程最小,显然走一遍的路径就是关键点构成虚树的直径
首先找出虚树的直径,用类似求树的直径的方法,从某个关键点出发dfs一遍求出一个端点,再dfs一遍求出另外一个端点并记录下直径。然后遍历直径,dp处理出直径上的端点遍历到其它关键点来回的长度,最终统计答案即可
CF1779F Xorcerer’s Stones
树形dp记录路径
首先我们分析一下操作。
对于奇树,进行一次操作后,其异或和不变;对于偶树,进行一次操作后,其异或和为0。
如果我们能让所有点异或和为0,只要在根节点再进行一次操作,就可以得到一种合法操作方案,考虑树形dp出是否存在一种方案,
d
p
u
,
j
dp_{u,j}
dpu,j表示以
u
u
u为根节点的子树内,是否存在异或和为
j
j
j的操作方案。合并很简单,但是对于
s
z
u
sz_u
szu为偶数时,不论其子树内操作如何,都可以进行一次操作,使得
d
p
u
,
0
=
1
dp_{u,0}=1
dpu,0=1。
对于方案的记录,我们开一个
p
r
e
v
,
j
x
o
r
k
=
j
pre_{v,jxork}=j
prev,jxork=j,表示
d
p
u
,
j
x
o
r
k
=
1
dp_{u,jxork}=1
dpu,jxork=1这种方案是在儿子异或和为
k
k
k的情况下,根节点异或和为
j
j
j的情况下合并转移而来的,因为dp时
u
u
u的儿子由前往后遍历转移,那么输出路径时就应该由后往前遍历
CF1312G Autocompletion
按题意建出一颗字典树,我们要求出的是每一个询问的答案,对于某一个结点
x
x
x,其实就是一个从根节点到
x
x
x路径最小步数的问题
很显然我们只能离线去求,我们发现题目问的是最小,并且父亲的最小步数是可以传递给儿子的,所以我们考虑dp
那么状态就不难设计了,重点是转移,假设我们现在遍历到点 u u u,两种转移:第一种直接从父亲到 u u u,花费步数1;第二种,从 u u u的某个直接或间接父亲自动补全转移过来,假设从 f a fa fa转移过来, d p u = m a x ( d p u , d p f a + d f n u − d f n f a − 1 ) dp_u=max(dp_u,dp_{fa}+dfn_u-dfn_{fa}-1) dpu=max(dpu,dpfa+dfnu−dfnfa−1),对于 d p f a + d f n f a − 1 dp_{fa}+dfn_{fa}-1 dpfa+dfnfa−1,前缀最大值优化即可做到O(1)转移,当 x x x在集合 S S S中的才进行 d f n x dfn_x dfnx++操作,否则只是传承父亲节点的 d f n dfn dfn
CF804D Expected diameter of a tree
对于两颗树
x
,
y
x,y
x,y,期望=任意两点连后直径和
/
(
s
z
x
⋅
s
z
y
)
/(sz_x\cdot sz_y)
/(szx⋅szy)
考虑任意两点
x
,
y
x,y
x,y(上面是树根,这里定义不一样),要求直径,易证为
m
a
x
(
l
e
n
f
i
n
d
(
x
)
,
l
e
n
f
i
n
d
(
y
)
,
d
p
x
+
d
p
y
+
1
)
max(len_{find(x)},len_{find(y)},dp_x+dp_y+1)
max(lenfind(x),lenfind(y),dpx+dpy+1)
l
e
n
f
i
n
d
(
x
)
len_{find(x)}
lenfind(x)表示
x
x
x所在树的直径长度,
d
p
x
dp_x
dpx表示
x
x
x所在树,离
x
x
x最远点的距离(易证最远点为树直径端点)
首先最朴素的做法就是预处理出树上的信息,然后每次查询,特判两点是否在同一颗树上,然后分别枚举两个树上的点,求得贡献,时间复杂度 O ( q n 2 ) O(qn^2) O(qn2)
令
m
a
x
n
=
m
a
x
(
l
e
n
f
i
n
d
(
x
)
,
l
e
n
f
i
n
d
(
y
)
)
maxn=max(len_{find(x)},len_{find(y)})
maxn=max(lenfind(x),lenfind(y))
如果
d
p
x
+
d
p
y
+
1
<
=
m
a
x
n
dp_x+dp_y+1<=maxn
dpx+dpy+1<=maxn,那么这两个点是哪两个已经不重要了,直接求得贡献,因此可以优化:枚举包含
x
x
x树的点,然后二分另一棵树到第pos小距离时大于maxn,并前缀和优化,
O
(
1
)
O(1)
O(1)算出
∑
d
p
y
\sum dp_y
∑dpy和
m
a
x
n
maxn
maxn所产生的贡献,最后贡献加上
d
p
x
∗
s
z
f
i
n
d
(
y
)
dp_x*sz_{find(y)}
dpx∗szfind(y),细节具体想
O
(
q
n
l
o
g
n
)
O(qnlogn)
O(qnlogn)
复杂度还是不够,根号分治,当两颗树大小都大于 n \sqrt n n时,直接离线预处理一下,因为这样的数不超过 n \sqrt n n颗,所以时间复杂度 O ( n n l o g n ) O(n\sqrt n logn) O(nnlogn),在线查询部分每次枚举大小较小的树, O ( q n l o g n ) O(q\sqrt n logn) O(qnlogn) 综合 O ( n n l o g n ) O(n\sqrt n logn) O(nnlogn)
在线离线差不多,在线部分:
int maxlen=std::max(len[x],len[y]);
ll ans=0;
for (int i=0;i<use[x].size();i++){
int pos=lower_bound(use[y].begin(),use[y].end(),maxlen-use[x][i])-use[y].begin();
ans+=1LL*pos*maxlen;
if (pos<use[y].size()){
ans+=1LL*dp[p[x][i]]*(use[y].size()-pos)+sum[y][use[y].size()-1]-(pos-1>=0?sum[y][pos-1]:0)+(use[y].size()-pos);
}
}
std::cout<<std::fixed<<std::setprecision(8)<<1.0*ans/sz[x]/sz[y]<<"\n";
这道题贡献计算并不难,难点在于优化,根号分治和排序后前缀和优化
2022济南C
先考虑兄弟节点,设
f
i
,
j
f_{i,j}
fi,j表示除自己外,兄弟节点选了
i
i
i 个,遍历到自己前还有
j
j
j个点(不考虑父亲及之前)的方案数
实现:先不考虑除谁,一次性背包求所有方案数,然后回退背包+组合求出
然后考虑上父亲及之前的点,树上遍历合并背包即可
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
constexpr ll mod=998244353;
ll fac[505];
ll ksm(ll x,ll p){
ll ans=1;
x%=mod;
while (p){
if (p&1) ans=ans*x%mod;
x=x*x%mod;
p>>=1;
}
return ans;
}
ll inv(ll x){
return ksm(x,mod-2);
}
void yrzr(){
int n;
std::cin>>n;
std::vector<std::vector<int>> e(n+1);
for (int i=1;i<n;i++){
int x,y;
std::cin>>x>>y;
e[x].push_back(y);
e[y].push_back(x);
}
std::vector<std::vector<ll>> dp(n+2,std::vector<ll>(n+2));
std::vector<int> sz(n+2),tol(n+2);
dp[1][1]=1;
auto dfs1=[&](auto self,int u,int fa)->void{
sz[u]=1;
for (auto v:e[u]){
if (v==fa) continue;
self(self,v,u);
sz[u]+=sz[v];
tol[u]++;
}
dp[1][1]=dp[1][1]*fac[tol[u]]%mod;
};
dfs1(dfs1,1,0);
auto dfs2=[&](auto self,int u,int fa)->void{
std::vector<std::vector<ll>> f(e[u].size()+2,std::vector<ll>(n+2));
f[0][0]=1;
for (auto v:e[u]){
if (v==fa) continue;
for (int i=tol[u];i>=1;i--){
for (int j=sz[u];j>=sz[v];j--){
f[i][j]=(f[i][j]+f[i-1][j-sz[v]])%mod;
}
}
}
for (auto v:e[u]){
if (v==fa) continue;
std::vector<ll> g(n+2);
for (int i=1;i<=tol[u];i++){
for (int j=sz[v];j<=sz[u];j++){
f[i][j]=(f[i][j]-f[i-1][j-sz[v]]+mod)%mod;
}
}
for (int i=0;i<=tol[u];i++){
for (int j=0;j<=sz[u];j++){
g[j+1]=(g[j+1]+f[i][j]*fac[i]%mod*fac[tol[u]-1-i]%mod)%mod;
}
}
for (int j=0;j<=n;j++){
for (int k=0;k<=sz[u]+1;k++){
if (j+k<=n){
dp[v][j+k]=(dp[v][j+k]+dp[u][j]*g[k]%mod)%mod;
}
}
}
for (int i=tol[u];i>=1;i--){
for (int j=sz[u];j>=sz[v];j--){
f[i][j]=(f[i][j]+f[i-1][j-sz[v]])%mod;
}
}
}
for (auto v:e[u]){
if (v==fa) continue;
self(self,v,u);
}
};
dfs2(dfs2,1,0);
for (int i=1;i<=n;i++){
ll sum=0;
for (int j=1;j<=n;j++){
sum=(sum+dp[i][j])%mod;
// std::cout<<dp[i][j]<<" ";
}
for (int j=1;j<=n;j++){
std::cout<<dp[1][1]*dp[i][j]%mod*inv(sum)%mod<<" ";
}
std::cout<<"\n";
}
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
fac[0]=1;
for (int i=1;i<=500;i++){
fac[i]=fac[i-1]*i%mod;
}
int T=1;
// std::cin>>T;
while (T--){
yrzr();
}
return 0;
}