1. 程式人生 > 實用技巧 >題解 AT4995 【[AGC034E] Complete Compress】

題解 AT4995 【[AGC034E] Complete Compress】

轉載註明來源:https://www.cnblogs.com/syc233/p/13603919.html


注意到資料範圍很小,所以我們可以列舉一個根,儘量讓所有點移動到這個點上。

將兩顆距離至少為 \(2\) 的棋子向中間移動一步,可能會出現兩種情況:

  • 它們的LCA為它們共同的祖先,則兩顆棋子一同向LCA移動一步。
  • 其中一顆棋子為LCA,則深度淺的棋子向下移動,深度深的棋子向上移動。

顯然第二種情況對於尋找最優解是不利的,為了讓所有點移動到根上,每個點都應向上跳。所以我們只考慮以第一種情況操作。


zzy:

這裡就涉及到一個經典的模型:有若干個元素被分成了若干個集合, 每次要找兩個在不同集合中的元素匹配然後消掉。

設所有集合的總大小為 \(sum\) ,最大集合的大小為 \(max\) ,有兩種情況:

  • \(sum-max\geq max\) ,每次選擇最大和次大的兩個集合內兩個元素消掉,消掉後還是這種情況。所以最多能消掉 \(\lfloor\frac{sum}{2}\rfloor\) 對元素。
  • \(sum-max<max\) ,把其他集合的元素全部用來消最大集合中的元素,最多能消掉 \(sum-max\) 對元素,最後最大集合中還剩餘 \(max\times 2-sum\) 個元素。

再回到這道題。

考慮樹上的一個點 \(u\) ,我們將它的子樹中的點移動到 \(u\) 上。對於 \(u\)

子樹中一個點 \(v\) ,我們必須對它進行 \(dis(u,v)\) 次操作才能將它移動到點 \(u\) 。這相當於將 \(u\) 子樹中每個有棋子的點 \(v\) 拆成了一個有 \(dis(u,v)\) 個元素的集合,每次在兩個集合中分別選擇兩個元素相消,則問題轉化為了經典模型。

但是由於樹結構的限制,有祖孫關係的兩個點對應的集合不能同時選擇。因此我們考慮樹形DP,將問題轉化為互不相交的子問題求解。

\(f_u\) 表示 \(u\) 的子樹內最多能消去多少對元素,\(dis_u\) 表示 \(u\) 的子樹中棋子拆成的集合的總大小,設 \(max\) 表示 \(u\) 的兒子對應的子樹中棋子拆成的集合的最大的總大小,即 \({\rm{max}}\{dis_v|v \in son_v\}\)

。則有轉移:

  • \(dis_u-max\geq max\) ,每次選擇兩個處於不同子樹的兩個元素相消,則 \(f_u=\lfloor\frac{sum}{2}\rfloor\)
  • \(dis_u-max<max\) ,先用其他子樹中的元素消最大子樹中的元素,這樣能消掉 \(dis_u-max\) 對元素,剩下的 \(max\times 2-dis_u\) 個元素全部在最大子樹中。在最大子樹 \(v\) 內部最多能消 \(f_v\) 對元素,則剩下的元素在最大子樹中能消 \(\lfloor\frac{{\rm{min}}(f_v\times 2,max\times 2-dis_u)}{2}\rfloor\) 對。則 \(f_u=dis_u-max+\lfloor\frac{{\rm{min}}(f_v\times 2,max\times 2-dis_u)}{2}\rfloor\)

\(root\) 為根時,當且僅當 \(f_{root}=\frac{dis_{root}}{2}\) ,即所有元素都消完時合法。


\(\text{Code}:\)

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define maxn 2005
#define Rint register int
#define INF 0x3f3f3f3f
using namespace std;
typedef long long lxl;

template <typename T>
inline void read(T &x)
{
	x=0;T f=1;char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
	x*=f;
}

struct edge
{
	int u,v,next;
}e[maxn<<1];

int head[maxn],k;

inline void add(int u,int v)
{
	e[k]=(edge){u,v,head[u]};
	head[u]=k++;
}

int n,a[maxn];
int siz[maxn],dis[maxn],f[maxn];

inline void dfs(int u,int fa)
{
	siz[u]=a[u];
	dis[u]=0;
	int son=-1;
	for(int i=head[u];~i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==fa) continue;
		dfs(v,u);
		siz[u]+=siz[v];
		dis[u]+=(dis[v]+=siz[v]);
		if(!~son||dis[son]<dis[v]) son=v;
	}
	if(!~son) return void(f[u]=0);
	if(dis[u]-dis[son]>=dis[son]) f[u]=dis[u]/2;
	else f[u]=dis[u]-dis[son]+min(f[son]*2,2*dis[son]-dis[u])/2;
}

char s[maxn];

int main()
{
	// freopen("AT4995.in","r",stdin);
	read(n);
	scanf(" %s",s+1);
	for(int i=1;i<=n;++i) a[i]=s[i]-'0';
	memset(head,-1,sizeof(head));
	for(int i=1,u,v;i<n;++i)
	{
		read(u),read(v);
		add(u,v);add(v,u);
	}
	int ans=-1;
	for(int i=1;i<=n;++i)
	{
		dfs(i,0);
		if(f[i]*2==dis[i]) ans=(!~ans||ans>f[i])?f[i]:ans;
	}
	printf("%d\n",ans);
	return 0;
}