题目
思路
树这个背景完全没用。根据 p r u f e r \rm prufer prufer 数列可知,唯一的限制条件就是度数和为 2 n − 2 2n-2 2n−2 。
可以转化为求 ∏ ( 1 + x d y ) \prod(1+x^dy) ∏(1+xdy) 的项数。考虑到度数 d ⩾ 1 d\geqslant 1 d⩾1,也可以是求 ∏ ( 1 + x d − 1 y ) \prod(1+x^{d-1}y) ∏(1+xd−1y) 的项数。
接下来的东西,就不是生成函数那么容易发现的。——对于某个
k
k
k,存在的项
x
k
y
i
x^ky^i
xkyi 满足
i
i
i 构成一个 区间!换句话说,如果第
i
i
i 个物品的权值是
v
i
=
d
i
−
1
v_i=d_i-1
vi=di−1,那么令
R
(
k
)
=
max
k
=
∑
i
∈
S
v
i
∣
S
∣
L
(
k
)
=
min
k
=
∑
i
∈
S
v
i
∣
S
∣
R(k)=\max_{k=\sum_{i\in S}v_i}|S|\\ L(k)=\min_{k=\sum_{i\in S}v_i}|S|
R(k)=k=∑i∈Svimax∣S∣L(k)=k=∑i∈Svimin∣S∣
则有:可以选出 c c c 个物品,使得其权值和为 k k k,当且仅当 L ( k ) ⩽ c ⩽ R ( k ) L(k)\leqslant c\leqslant R(k) L(k)⩽c⩽R(k) 。
为什么会有这种性质呢?因为有
d
i
=
1
d_i=1
di=1 即
v
i
=
0
v_i=0
vi=0 的存在,相当于可以白嫖一个物品。不妨设共有
z
z
z 个
v
=
0
v=0
v=0 的物品,我们只需要证明
R
(
k
)
−
L
(
k
)
⩽
2
z
+
1
R(k)-L(k)\leqslant 2z+1
R(k)−L(k)⩽2z+1
因为 R ( k ) R(k) R(k) 一定含有 z z z 个 v = 0 v=0 v=0 的点,将其去掉一部分,可以得到物品数量在 [ R ( k ) − z , R ( k ) ] [R(k)-z,R(k)] [R(k)−z,R(k)] 内的方案。类似地, L ( k ) L(k) L(k) 一定不含有 v = 0 v=0 v=0 的点,可以凑出物品数量在 [ L ( k ) , L ( k ) + z ] [L(k),L(k)+z] [L(k),L(k)+z] 中的方案。
上面的充分性,并不难发现。而对于不等式本身的证明,极为巧妙!对于任意一个选择物品的方案,设其权值和为
k
k
k,数量为
c
c
c,则有
−
z
⩽
k
−
c
⩽
z
−
2
-z\leqslant k-c\leqslant z-2
−z⩽k−c⩽z−2
因为
k
−
c
=
∑
i
∈
S
(
v
i
−
1
)
k-c=\sum_{i\in S}(v_i-1)
k−c=∑i∈S(vi−1),显然最小值是
v
i
−
1
v_i-1
vi−1 为负数的求和,即
v
i
=
0
v_i=0
vi=0 的数量
z
z
z 乘
−
1
-1
−1 。而最大值,应当是
S
S
S 包含所有
v
i
⩾
1
v_i\geqslant 1
vi⩾1 的数。考虑到
v
i
=
0
v_i=0
vi=0 的
v
i
v_i
vi 也可以被计算,所以实际上
k
−
c
⩽
∑
v
i
−
∑
[
v
i
⩾
1
]
=
(
n
−
2
)
−
(
n
−
z
)
=
z
−
2
\begin{aligned} k-c &\leqslant \sum v_i-\sum [v_i\geqslant 1]\\ &=(n-2)-(n-z)\\ &=z-2 \end{aligned}
k−c⩽∑vi−∑[vi⩾1]=(n−2)−(n−z)=z−2
那么 k − L ( k ) k-L(k) k−L(k) 和 k − R ( k ) k-R(k) k−R(k) 也应该在 [ − z , z − 2 ] [-z,z-2] [−z,z−2] 内。二者的差值,即 R ( k ) − L ( k ) R(k)-L(k) R(k)−L(k),也就不会超过区间长度 2 z − 1 2z-1 2z−1 了。
完成了上面的所有证明,终于进入求解环节。因为 ∑ v = n − 2 \sum v=n-2 ∑v=n−2,所以最多有 O ( n ) \mathcal O(\sqrt n) O(n) 个不同的 v v v 。无非是一个部分背包,求 d p s − i v + i dp_{s-iv}+i dps−iv+i 的最值,使用滑动窗口就好了。
时间复杂度
O
(
n
n
)
\mathcal O(n\sqrt{n})
O(nn) 。用官方题解的话来说:
W
e
a
r
e
d
o
n
e
.
\rm We\; are\; done.
Wearedone.(我们被干了。)
代码
#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int_ readint(){
int_ a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MaxN = 200005;
int v[MaxN], dp[MaxN];
int tmp[MaxN], q[MaxN];
const int infty = (1<<30)-1;
int main(){
int n = readint();
memset(v+1,-1,(n+1)<<2);
rep(i,3,n<<1) ++ v[readint()];
sort(v+1,v+n+1), dp[0] = 0;
/* compute L(k) firstly */ ;
rep(i,1,n) dp[i] = infty;
for(int l=1,r; l<=n; l=r){
for(r=l; v[r]==v[l]; ++r);
if(!v[l]) continue; // useless
for(int i=0; i<v[l]; ++i){ // remainder
int fro = 0, bac = -1;
for(int j=0; j<=(n-i)/v[l]; ++j){
while(fro <= bac && dp[q[bac]]+j
-q[bac]/v[l] > dp[j*v[l]+i])
-- bac; // pop_back()
q[++ bac] = j*v[l]+i;
while(j-q[fro]/v[l] > r-l)
++ fro; // too far away
tmp[j*v[l]+i] = dp[q[fro]]
+j-q[fro]/v[l]; // update
}
}
swap(dp,tmp); // dp = tmp
}
int_ ans = 0;
rep(i,0,n-2) if(dp[i] <= n)
ans -= dp[i]; // R-L+1
/* compute R(k) secondly */ ;
rep(i,1,n) dp[i] = -infty;
for(int l=1,r; l<=n; l=r){
for(r=l; v[r]==v[l]; ++r);
if(v[l] == 0){ // must choose
rep(i,0,n) dp[i] += r-l;
continue; // avoid swap(dp,tmp)
}
for(int i=0; i<v[l]; ++i){ // remainder
int fro = 0, bac = -1;
for(int j=0; j<=(n-i)/v[l]; ++j){
while(fro <= bac && dp[q[bac]]+j
-q[bac]/v[l] < dp[j*v[l]+i])
-- bac; // pop_back()
q[++ bac] = j*v[l]+i;
while(j-q[fro]/v[l] > r-l)
++ fro; // too far away
tmp[j*v[l]+i] = dp[q[fro]]
+j-q[fro]/v[l];
}
}
swap(dp,tmp); // dp = tmp
}
rep(i,0,n-2) if(0 <= dp[i])
ans += dp[i]+1; // R+1-L
printf("%lld\n",ans);
return 0;
}