题解 [AGC034E] Complete Compress
完了,连紫题都做不出来了/kk
首先观察数据范围可以猜到最后大概是枚举汇集点,然后一遍树形 DP check 是否合法
那么问题变为如何树形 DP
我一开始认为每个子树在经过若干次内部操作后能对子树外贡献的操作次数是一个区间 \([l, r]\)
但是假的离谱,因为实际贡献区间是一车区间的并
这个东西也许是可以 bitset 暴力做到一个较劣的复杂度的
然后还是正解:
首先发现操作有祖先关系的两个点一定不优
那么就是每次选两个点,将它们同时向 lca 处移动一步
那么(在当前指定的根节点)问题就变成了一个我没见过的经典问题:
- 给定若干个集合,每次从两个不同的集合中分别选一个消掉,求最多消掉多少个:
考虑最大的那个集合,当这个集合 \(\geqslant \lfloor\frac{sum}{2}\rfloor\) 时,可以全部消完
否则只能消完 \(sum-max\) 对元素
回到这道题,令 \(f_i\) 为 \(i\) 子树内最多能消掉多少对元素
还是上面模型,如果最大的子树还有剩就尽量在内部消
\[f_i=\begin{cases}
\lfloor\frac{sum}{2}\rfloor&max\geqslant \lfloor\frac{sum}{2}\rfloor \\
sum-max+\min(f_{maxi}, \lfloor\frac{max-(sum-max)}{2}\rfloor)&\tt other \\
\end{cases}\]
复杂度 \(O(n^2)\),可以换根 DP 优化掉一个 \(n\)
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 2010
#define ll long long
//#define int long long
int n;
char col[N];
int head[N], f[N], siz[N], dis[N], ecnt, ans=INF;
struct edge{int to, next;}e[N<<1];
inline void add(int s, int t) {e[++ecnt]={t, head[s]}; head[s]=ecnt;}
void dfs(int u, int fa) {
int sum=0, mx=0, maxi;
siz[u]=col[u];
for (int i=head[u],v; ~i; i=e[i].next) {
v = e[i].to;
if (v==fa) continue;
dfs(v, u);
siz[u]+=siz[v];
sum+=dis[v]+siz[v];
if (dis[v]+siz[v]>mx) mx=dis[v]+siz[v], maxi=v;
}
if (mx<=sum-mx) f[u]=sum/2;
else f[u]=sum-mx+min(f[maxi], (2*mx-sum)/2);
dis[u]=sum;
}
void solve(int rot) {
dfs(rot, 0);
int sum=0;
for (int i=head[rot],v; ~i; i=e[i].next) {
v = e[i].to;
sum+=dis[v]+siz[v];
}
if (f[rot]*2==sum) ans=min(ans, f[rot]);
}
signed main()
{
scanf("%d%s", &n, col+1);
memset(head, -1, sizeof(head));
for (int i=1; i<=n; ++i) col[i]-='0';
for (int i=1,u,v; i<n; ++i) {
scanf("%d%d", &u, &v);
add(u, v); add(v, u);
}
for (int i=1; i<=n; ++i) solve(i);
printf("%d\n", ans==INF?-1:ans);
return 0;
}