[ARC125F]Tree Degree Subset Sum

题目

传送门 to AtCoder

思路

树这个背景完全没用。根据 p r u f e r \rm prufer prufer 数列可知,唯一的限制条件就是度数和为 2 n − 2 2n-2 2n2

可以转化为求 ∏ ( 1 + x d y ) \prod(1+x^dy) (1+xdy) 的项数。考虑到度数 d ⩾ 1 d\geqslant 1 d1,也可以是求 ∏ ( 1 + x d − 1 y ) \prod(1+x^{d-1}y) (1+xd1y) 的项数。

接下来的东西,就不是生成函数那么容易发现的。——对于某个 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=di1,那么令
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=iSvimaxSL(k)=k=iSviminS

则有:可以选出 c c c 个物品,使得其权值和为 k k k当且仅当 L ( k ) ⩽ c ⩽ R ( k ) L(k)\leqslant c\leqslant R(k) L(k)cR(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 zkcz2

因为 k − c = ∑ i ∈ S ( v i − 1 ) k-c=\sum_{i\in S}(v_i-1) kc=iS(vi1),显然最小值是 v i − 1 v_i-1 vi1 为负数的求和,即 v i = 0 v_i=0 vi=0 的数量 z z z − 1 -1 1 。而最大值,应当是 S S S 包含所有 v i ⩾ 1 v_i\geqslant 1 vi1 的数。考虑到 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} kcvi[vi1]=(n2)(nz)=z2

那么 k − L ( k ) k-L(k) kL(k) k − R ( k ) k-R(k) kR(k) 也应该在 [ − z , z − 2 ] [-z,z-2] [z,z2] 内。二者的差值,即 R ( k ) − L ( k ) R(k)-L(k) R(k)L(k),也就不会超过区间长度 2 z − 1 2z-1 2z1 了。

完成了上面的所有证明,终于进入求解环节。因为 ∑ v = n − 2 \sum v=n-2 v=n2,所以最多有 O ( n ) \mathcal O(\sqrt n) O(n ) 个不同的 v v v 。无非是一个部分背包,求 d p s − i v + i dp_{s-iv}+i dpsiv+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;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值