题解 AGC034E Complete Compress
看见 \(n\le 2000\),可以枚举答案点。
注意到我们把答案点定为根之后的移动可以分为两种。
- 两个点分别向深度小的方向移动。
- 一个点向深度小的方向移动,一个点向深度大的方向移动。
容易证明第二种移动不会采用,因为把向深度大的方向移动的点移回去时对应的点可以和向深度小的方向移动的那个点匹配进行一次移动,这样答案显然更优。
于是就只有第一种操作了。
这里需要用到一个结论。就是有 \(n\) 个集合,第 \(i\) 个集合有 \(a_i\) 个元素,每次操作选择两个不同的非空集合,各拿走一个元素,不能操作时结束,问最多进行多少次操作。
这个问题分两种情况考虑,设 \(a_i\) 的最大值为 \(maxn\),所有 \(a_i\) 的和为 \(sum\)。
若 \(2maxn\le sum\),那么除了全是 \(1\) 的情况,其他的把最大值和非严格次大值取走一个之后还能回到这种情况。而全是 \(1\) 的情况一定是 \(\lfloor \frac{sum}{2} \rfloor\),所以答案就是$\lfloor \frac{sum}{2} \rfloor $。
若 \(2maxn>sum\) ,那么肯定是把剩下的所有和最大值配对,操作数是 \(sum-maxn\) 。
有了这个结论之后,我们求出 \(g_i=\sum_{x\in sub(i)} v_x\times dist(x,i)\) , \(v_x\) 表示 \(x\) 是否含有棋子。那么我们就可以把每个 \(dist(x,i)\) 当作上面例子中的 \(a\) 来 dp。
设 \(f_i\) 表示 \(i\) 的子树最多进行多少次操作,那么合法等价于 \(2f_{root}=g_{root}\),答案即为 \(f_{root}\)。
考虑转移,设 \(siz_i=\sum_{x\in sub(i)}v_x\),求出所有儿子中最大的 \(g_v+siz_v\),设为 \(maxn\),\(sum\) 为所有儿子的 \(g_v+siz_v\) 之和。
若 \(2maxn\le sum\),则 \(f_i=\lfloor \frac{sum}{2} \rfloor\) 。
若 \(2maxn<sum\) 则,设 \(i\) 最大儿子为 \(u\) ,\(f_i=sum-maxn+min(f_u,\lfloor \frac{2maxn-sum}{2} \rfloor)\)。
直接树形 dp 即可,复杂度 \(O(n^2)\)
\(\sf{Code}\)
#include<bits/stdc++.h>
#define N 2001001
#define MAX 2001
using namespace std;
typedef long long ll;
typedef double db;
const ll mod=998244353,inf=1e18,inv2=(mod+1)/2;
const db eps=1e-9;
inline void read(ll &ret)
{
ret=0;char c=getchar();bool pd=false;
while(!isdigit(c)){pd|=c=='-';c=getchar();}
while(isdigit(c)){ret=(ret<<1)+(ret<<3)+(c&15);c=getchar();}
ret=pd?-ret:ret;
return;
}
ll n,x,y,ans=inf,f[N],g[N],dep[N],siz[N];
ll a[N];
char s[N];
vector<ll>v[N];
inline void dfs(ll ver,ll fa)
{
g[ver]=0;
siz[ver]=a[ver];
f[ver]=0;
for(int i=0;i<v[ver].size();i++)
{
ll to=v[ver][i];
if(to==fa)
continue;
dfs(to,ver);
siz[ver]+=siz[to];
g[ver]+=g[to]+siz[to];
}
if(fa&&v[ver].size()==1)
return;
ll maxn=-inf,maxpos=0;
for(int i=0;i<v[ver].size();i++)
{
ll to=v[ver][i];
if(to==fa)
continue;
if(g[to]+siz[to]>maxn)
{
maxn=g[to]+siz[to];
maxpos=to;
}
}
if(maxn*2<=g[ver])
f[ver]=g[ver]>>1;
else
f[ver]=(g[ver]-maxn)+min(f[maxpos],(2*maxn-g[ver])/2);
return;
}
signed main()
{
read(n);
scanf("%s",s+1);
for(int i=1;i<n;i++)
{
read(x);
read(y);
v[x].push_back(y);
v[y].push_back(x);
}
for(int i=1;i<=n;i++)
a[i]=(s[i]&15);
for(int i=1;i<=n;i++)
{
dfs(i,0);
if(!(g[i]&1)&&f[i]==g[i]/2)
ans=min(ans,f[i]);
}
if(ans==inf)
ans=-1;
printf("%lld\n",ans);
exit(0);
}