[??记录]AT2304 [AGC010C] Cleaning

command_block

2021-04-28 07:11:57

Personal

**题意** : 一棵树,第 $i$ 个节点上有 $a_i$ 个石头。 每次可以选择两个不同的叶节点(度数为 $1$ 的节点),将两点间路径上的所有节点都取走一个石头,如果有某个点上没石头这个操作就不能进行。 判定能不能取完所有石头。 $n\leq 10^5$ ,时限$\texttt{2s}$。 ------------ 先任找一个不是叶子的点为根。(若 $n=2$ 则都是叶子,特判) 记 $f_u$ 表示方案中穿过 $u$ 的路径数目。对于叶节点,有 $f_u=a_u$。 对于非叶节点,记 $s_u=\sum\limits_{u\rightarrow v}f_v$ ,即从儿子延伸上来的路径条数。 这些路径可以选择两两合并,也可以继续延伸。可以列出方程 : $$a_u=\dfrac{s_u-f_u}{2}+f_u=\dfrac{s_u+f_u}{2}$$ $$f_u=2a_u-s_u$$ 根据这个方程,可以用一次 $\rm dfs$ 求出所有的 $f,s$。 然后考虑如何判定,一个显然的条件是 $0\leq f_u,s_u$。 此外,$f_u,s_u$ 各有上界。记 $c_u$ 为子树内叶节点的权值和,则 : - $f_u\leq c_u$ - $\dfrac{s_u-f_u}{2}\leq s_u-\max\limits_{u\rightarrow v}f_v)$ 这个套路和 [[DS记录]P4338 [ZJOI2018]历史](https://www.luogu.com.cn/blog/command-block/ds-ji-lu-p4338-zjoi2018-li-shi) 一致。 当 $u$ 点为根时,额外要求 $f_u=0$。 若满足上述条件,不难归纳证明可以构造出合法方案。 复杂度 $O(n)$。 ```cpp #include<algorithm> #include<cstdio> #include<vector> #define pb push_back #define ll long long #define MaxN 100500 using namespace std; vector<int> g[MaxN]; ll a[MaxN],f[MaxN],c[MaxN]; bool fl; void dfs(int u,int fa) { if (g[u].size()==1){ c[u]=f[u]=a[u]; return ; } ll s=0,mx=0; for (int i=0,v;i<g[u].size();i++) if ((v=g[u][i])!=fa){ dfs(v,u); s+=f[v]; c[u]+=c[v]; mx=max(mx,c[v]); } f[u]=2*a[u]-s; if (f[u]<0||s<0||f[u]>c[u]||(s-f[u])/2>min(c[u]/2,c[u]-mx))fl=0; if (!fa&&f[u]>0)fl=0; } int n; int main() { scanf("%d",&n); for (int i=1;i<=n;i++)scanf("%lld",&a[i]); for (int i=1,u,v;i<n;i++){ scanf("%d%d",&u,&v); g[u].pb(v);g[v].pb(u); } if (n==2){ puts(a[1]==a[2] ? "YES" : "NO"); return 0; } int rt=0; for (int i=1;i<=n;i++) if (g[i].size()>1)rt=i; fl=1;dfs(rt,0); puts(fl ? "YES" : "NO"); return 0; } ```