2020-08-04集训题目题解

Niyaz and Small Degrees

题目传送门

题目大意

给出一个 (n) 个点的树,边有边权,对于 (xin[0,n-1]) ,问删掉某些边使得每个点的度数不大于 (x) 并使得删掉的边边权之和最小。

(nle 2.5 imes 10^5)

思路

应该算是今天题目里面码量最大的了吧。。。

我们发现,对于每一个 (x) ,我们都可以用一个(Theta(nlog n))的dp解决。我们可以设 (f_{i,0/1}) 表示点 (i) 不删/删与父亲相连的边子树内都满足条件的最小边权和。

然后dp转移的时候,我们首先选出那些 (f_{v,1}+wle f_{v,0}) 的儿子,其中 (w)(u o v) 的边权,对于这些儿子肯定选删掉这些边,然后对于必须要删的边,我们可以选出 (f_{v,1}+w) 最少的儿子砍掉,其它就直接算上就好了。

但是 (Theta(n^2log n)) 肯定要炸的。我们不难想到一个优化,如果 (x) 递增的话,那么,对于一个度数不大于 (x) 的点肯定不用考虑,所以可以在删掉这些点的森林里面进行dp。然后。。。我们就做完了。我们发现这样做的时间复杂度为 (Theta(sum_{i=1}^{n} deg_ilog n)=Theta(nlog n)) 。。。

不过因为它复杂度的玄学,所以在实现的时候一定要小心,要是不小心搞许多没有用的操作时间复杂度就会降为(Theta(n^2log n))

( exttt{Code})

代码戳这里打开

Harry Vs Voldemort

题目传送门

题目大意

给出一个 (n) 个点的树,问有多少个三元组 ((x,y,w)) 满足 (x o w)(y o w) 的路径并且没有交集。有 (m) 加边,每次加完边后输出答案

(n,mle 10^5)

思路

对于一个树,我们可以想到对于钦定点 (u)(w) 的答案就是:

[n(n-1)^2-sum_{vin son_u} siz[v]^2-(n-siz[u])^2 ]

考虑对于一个图怎么做。下面设(a)(w)所在的点双连通分量,(siz[a])为边双连通分量(a)里面点的个数。很显然的是:

  1. 如果 (u,v,w) 在同一边双,那么显然可以,贡献即为
    (A_{siz[a]}^3=siz[a](siz[a]-1)(siz[a]-2))

  2. 如果 (u,w) 在同一个边双连通分量,那么 (v) 可以取该点双外任意一点,答案即为(2siz[a](siz[a]-1)(n-siz[a]))。这里乘(2)是因为我要从(u,v)中选一个放点双里面那个点。

  3. 如果(u,v,w) 在不同点双,那么就跟树上情况一样了,贡献即为 (siz[a]((n-siz[a])^2-w[a]))(w[a])表示在(a)一侧的子树平方和。对于一个树而言,(w[u]=sum_{vin son_u} siz[v]^2+(n-siz[u])^2)

我们发现加入一条边相当于把一条点双构成的链又合成一个点双。这个可以用并查集维护。

时间复杂度 (Theta(nalpha(n)))

( exttt{Code})

代码戳这里打开

Ribbons on Tree

题目传送门

题目大意

给出一个 (n) 个点的树,每次可以选出两个点把该路径所有边都染上颜色。每个点只能被选一次。问最后所有边都染上颜色的方案数。答案对 (10^9+7) 取模。

(nle 5000)

思路

终于明白了什么叫做看见计数想容斥(手动狗头)。。。

我们发现最后的答案其实就是:

[sum_{i=0}^{n-1} f_i(-1)^i ]

其中 (f_i) 表示至少有 (i) 条边没有染到的方案数。

这是因为:

[sum_{i=0}{n}inom{n}{i}(-1)^i=[n=0] ]

我们考虑怎么求。一个一个求 (f_i) 肯定是不好做的,我们可以考虑一个一个子树对答案的贡献。

我们可以设 (g_{u,i}) 表示点(u)在一个大小为(i)的联通块时对答案的贡献(不包括当前联通块),特殊的,(g_{u,0})表示(u)切掉与父亲的边时的贡献。我们发现我们还需要知道一个大小为(i)的联通块有多少种两两配对的方法,这个很简单,我们发现其实就是 ((i-1)(i-3)(i-5)...) ,我们暂且设为 (h(i)) 吧。

我们可以得到转移式:

[g_{u,i}gets sum_{j=0,vin son_u}^{i} g_{v,j}g_{u,i-j} ]

[g_{u,0}gets sum_{i=1} -g_{u,i}h(i) ]

乘上 (-1) 是因为相当于至少没有染到的边增加了。最后的答案其实就是(-g_{1,0}),这里乘上(-1)是因为点(1)其实并没有父亲。

于是,我们就可以做到(Theta(n^2))了。

( exttt{Code})

代码戳这里打开

XOR Tree

题目传送门

题目大意

给出一个(n)个点的树,边有边权,每次可以选出一个链让上面的边的边权异或上任意值。求出最少操作次数使得最后所有边的边权都变为(0)

(nle 10^5)

思路

首先有一个特别巧妙的转换,就是我们定义一个点的点权为与他相连的所有边的边权异或和,那么所有边边权为(0)就等价于所有点点权为(0)。这个可以使用归纳法证明,从叶子开始,这里就不赘述了。

很显然的是,我们让一条链异或上一个值就相当于链两端点权异或上该值。因为中间节点都会有两条边异或上该值,就不会有影响。那么,我们显然可以先让点权相同的点两两抵消,最后剩下最多(16)个两两不同的点权需要我们消掉。

我们就可以考虑状压了,我们设 (f[S])表示使状态(S)里面所有点权都变为(0)的最小操作次数,很显然,如果可行的话答案就是(|S|-1),方法就是两两抵消一下。我们发现这个还可以 dp,dp转移式就是:

[f[S]gets min{f[T]+f[Sotimes T]} ]

其中(T)(S)的子集,(Sotimes T)其实就是(T)关于(S)的补集。

然后我们就可以在(Theta(3^{16}+n))的时间复杂度内解决这个问题。

( exttt{Code})

代码戳这里打开

Leftmost Ball

题目传送门

题目大意

给出 (n) 种颜色,每种颜色 (k) 个小球,然后将这(n imes k)个小球排序,并将每一种颜色的小球的第一个染为(0)。注意,一个小球只有颜色之间的差别。

(n,kle 2000)

思路

我们首先发现一个性质:对于任何一个前缀,(0)的个数总是比其他颜色的小球个数要多。这也是这个序列的充必条件。

我们可以定义 (f_{i,j}) 表示放了 (i)(0),放完了 (j) 种颜色的方案数。可以得到转移式:

[f_{i,j}=f_{i-1,j}+f_{i,j-1}inom{n imes k-(k-1)(j-1)-i-1)}{k-2} ]

最后的答案即为(f_{n,n} imes n!)

这里解释一下为什么。(f_{i-1,j})就是我再放一个(0),后面那个就是从剩下的位置选出当前颜色所需的位置然后放进去。可是为什么是选(k-2)个呢?这是因为我们dp的过程之中其实并没有考虑不同颜色之间的差别(我们并不知道这个(0)是属于哪一种颜色的),我们想要区分的话就必须把非(0)的第一个小球先固定好,放在最开头。

时间复杂度(Theta(n imes k))

( exttt{Code})

代码戳这里打开

On the Bench

题目传送门

题目大意

给出一个长度为 (n) 的序列 (a_{1,2,...,n}),问有多少种排列方法使得任意相邻两数的乘积不为完全平方数。

(nle 300,a_ile 10^9)

思路

我们首先转换一下题意,对于每一个(a_i)我们可以去掉它的平方因子,那么答案就变为了有多少种方法使得任意相邻两数不同。然后我们看到计数想容斥,我们就可以转换成求:

[sum_{i=0}^{n-1} f_i (-1)^i ]

其中(f_i)表示至少有(i)对位置两两相同的方案数。

我们发现,如果我们设(g_{i,j})为前(i)元素分成(j)块并保证块里面的元素两两相同的方案数,而且这里的块是没有顺序的。那么(f_{n-k}=g_{m,k}k!),其中(m)表示有多少种不同的元素。

我们考虑如何求出(g_{m,k}),我们如果设(t_i)表示第(i)种元素有多少个的话可以得到转移式:

[g_{i,j}=sum_{1le kle j}g_{i-1,j-k}inom{t_i-1}{k-1} imes t_i!/k! ]

这个应该很好懂,就是一个插板法,这里就不赘述了。于是,我们就可以在(Theta(nsqrt w+n^3))的时间复杂度内解决这个问题,其中(w)是最大权值。

( exttt{Code})

代码戳这里打开

Wandering TKHS

题目传送门

题目大意

给出一个 (n) 个节点的树,对于每个节点,求出要从该点走到点(1)的步数,这里的步数定义为在当前走过的点里面选出一个未走过的点且编号最小的点走到该点的总次数。

(nle 2 imes 10^5)

思路

我们定义(mx[u])表示点(u)到点(1)上的权值最大值,(ans[u])为从(u)出发的答案,(que(u,i))表示(u)子树内只经过编号不大于(i)能经过的节点数。那么,我们来考虑几种情况:

  • (mx[u]>mx[fa[u]])

其实也等价于 (mx[u]=u)

那么可以想到的是,对于(u)的一个儿子(v),(ans[v]=ans[u]+que(v,u)-que(v,mx[fa[u]]))。解释一下,就是说因为从(v)出发一定会去把比(u)小的点先遍历了,但是从(u)出发碰到(mx[fa[u]])的时候也会先去(v)里面把比(mx[fa[u]])小的先走了,两者算重了,于是减掉就好了。

  • (mx[v]>mx[u])

可以想到的是,我从(u)出发一定不会经过(v),所以就需要加上(que(v,u)),不过需要注意的是我们无论如何都肯定需要经过(v),所以不能直接用(que(v,u))

注意:以上两种情况互不干扰!!! 所以需要两种情况都考虑。


于是,一个问题就是我们如何快速求出(que(u,i)),一种方法是直接线段树合并,但是其实并没有必要,我们可以直接爆搜,储存一下就好了。因为只有在(u=mx[u]iff mx[u]>mx[fa[u]])的时候才会调用,并且每次搜索最多只会搜到小于等于(u)的点,所以时间复杂度其实是(Theta(n))的。

于是,我们就可以在(Theta(n))的时间复杂度内解决这个问题。

( exttt{Code})

代码戳这里打开

原文地址:https://www.cnblogs.com/Dark-Romance/p/13436923.html