[ARC125F] Tree Degree Subset Sum

零、前言

自闭场,\(\tt D\) 想复杂白给,结束前 \(10\) 分钟想出正确做法忘写树状数组\(\tt C,E\) 赛后随便切 \(...\)

不要硬刚一道题,不要完全看过题人数来决定你做哪道题,\(\tt think\ twice,code\ once\)

\(\tt zxy\) 掉大分》正在连载中 \(...\)

一、题目

点此看题

给定 \(n\) 个点的树,每个点有一个度数 \(d_i\),问有多少个满足下列条件的二元组 \((x,y)\)

  • \(0\leq x\leq n\),并且存在选出 \(x\) 个点的方式使得其度数和为 \(y\)

\(n\leq 2\cdot 10^5\)

二、解法

首先我声明树这个背景没有任何卵用,根据 \(\tt purfer\) 序列的理论,任何一种度数序列都能对应至少一棵树,问题又只和度数序列有关,既然树对度数序列没有任何限制那么可以不管他。

我们把所有 \(d_i\) 减少 \(1\),那么问题变成了 \(n\) 个物品体积总和为 \(n-2\) 的背包问题,最烦的是要记录选了多少个物品。

Lamma:设 \(L(x)\) 表示总体积为 \(x\) 的最少物品数,\(R(x)\) 表示总体积为 \(x\) 的最多物品数,那么对于选出 \(t\) 个物品总和为 \(x\),当且仅当 \(L(x)\leq t\leq R(x)\) 是可以实现的。

证明:必要性易得,下证充分性。

设体积为 \(0\) 的物品一共有 \(z\) 个,可以转证下列问题:

\[R(x)-L(x)\leq 2z+1 \]

这是因为 \(R(x)\) 通过去掉体积为 \(0\) 的物品可以得到 \(t\in[R(x)-z,R(x)]\) 的方案,\(L(x)\) 可以通过增加体积为 \(0\) 的物品得到 \(t\in[L(x),L(x)+z]\) 的方案。

考虑任意一种背包方案权值和为 \(k\),数量为 \(c\),那么有:

\[-z\leq k-c\leq z-2 \]

因为让 \(k-c\) 最小直接选那些体积为 \(0\) 的物品,让 \(k-c\) 最大需要选体积大于 \(0\) 的物品:

\[k-c=\sum d_i-\sum [d_i> 0]=n-2-(n-z)=z-2 \]

那么 \(k-L(x)\)\(k-R(x)\) 一定在 \([-z,z-2]\) 这个范围里,所以 \(R(x)-L(x)\) 不超过 \(2z-2\),证毕。

只需要用 \(dp\) 求出 \(L(x),R(x)\) 即可,因为物品种类数不超过 \(\sqrt n\),对于每一种物品,可以把体积按余数分类,对于每一种余数跑单调队列即可,时间复杂度 \(O(n\sqrt n)\)

三、总结

首先排除不必要的背景,比如本题树的背景对原问题没有任何限制那么是可以排除的。

对于难做的点,找必要条件猜结论。

最后物品种类数很少的背包问题,每种物品分别处理,按余数分类之后解决。

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 200005;
#define ll long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,z,c[M],d[M],p[M],mi[M],mx[M],dp[M];ll ans;
signed main()
{
	n=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		d[u]++;d[v]++;
	}
	for(int i=1;i<=n;i++)
	{
		d[i]--,c[d[i]]++;
		mi[i]=1e9,mx[i]=-1e9;
		if(!d[i]) z++;
	}
	mi[0]=mx[0]=0;
	//cal mx
	for(int i=1;i<=n;i++)
	{
		int x=c[i];
		if(!x) continue;
		for(int j=0;j<=n;j++) dp[j]=mx[j]; 
		for(int j=0;j<i;j++)
		{
			int l=1,r=0,nw=j;
			for(int k=0;nw<=n;k++,nw+=i)
			{
				while(l<=r && p[l]<nw-i*x) l++;
				if(l<=r) mx[nw]=max(mx[nw],dp[p[l]]+k-p[l]/i);
				while(l<=r && dp[p[r]]-p[r]/i<=dp[nw]-nw/i) r--;
				p[++r]=nw;
			}
		}
	}
	//cal mi
	for(int i=1;i<=n;i++)
	{
		int x=c[i];
		if(!x) continue;
		for(int j=0;j<=n;j++) dp[j]=mi[j]; 
		for(int j=0;j<i;j++)
		{
			int l=1,r=0,nw=j;
			for(int k=0;nw<=n;k++,nw+=i)
			{
				while(l<=r && p[l]<nw-i*x) l++;
				if(l<=r) mi[nw]=min(mi[nw],dp[p[l]]+k-p[l]/i);
				while(l<=r && dp[p[r]]-p[r]/i>=dp[nw]-nw/i) r--;
				p[++r]=nw;
			}
		}
	}
	//cal answer
	for(int i=0;i<=n;i++)
		if(mi[i]<=mx[i])
			ans+=mx[i]-mi[i]+1+z;
	printf("%lld\n",ans);
}
posted @ 2021-08-23 12:11  C202044zxy  阅读(166)  评论(0编辑  收藏  举报