1. 程式人生 > 其它 >【洛谷P6805】春季大掃除

【洛谷P6805】春季大掃除

題目

題目連結:https://www.luogu.com.cn/problem/P6805
春季大掃除也許是我們一生中最無聊的事情之一。當然,對於 Flóra 和她的母親而言,今年的春季大掃除要有意思得多。因為她們在地毯下發現了一張已被灰塵覆蓋的樹形地圖。
這棵樹有 \(N\) 個節點,節點從 \(1\)\(N\) 進行編號,這 \(N\) 個點通過 \(N-1\) 條邊相連。這些邊上都積累了過多的灰塵,因此 Flóra 的母親準備對這棵樹進行清理。
清理這棵樹的過程是這樣的:Flóra 的母親每次在這棵樹上選擇兩個葉子節點(定義一棵樹的葉子節點為只與恰好一個點直接相連的點),並對這兩個葉子點路徑上的所有邊進行清理。如果這條路徑上有 \(d\)

條邊,則清理的費用為 \(d\)。當這棵樹上的所有邊都被清理後,這棵樹的清理過程就完成了。清理這棵樹的總費用即為各次清理的費用之和。
因為她想保護這棵樹的葉子節點,因此對於每個葉子節點,她最多隻會選擇一次。
Flóra 認為原來的樹過於簡單,她決定對原始的樹進行一些改造。在第 \(i\) 次改造中,她在原始的樹的基礎上添加了 \(D_i\) 個葉子節點。具體來說,她會在原始的樹上選擇一個節點,並在該點與新的葉子節點之間連線一條邊。需要注意的是,在新增新的葉子節點的過程中,原來的一些節點將不再是葉子節點。
現在你需要幫助 Flóra 求出清理改造後的樹的最小費用。
\(N,Q,\sum D\leq 10^5\)

思路

\(1\) 為根。
首先無解當且僅當有奇數個葉子。證明的話考慮 \(1\) 的每一個兒子,假設他們子樹內的葉子數量分別為 \(a_1,a_2,\cdots a_k\)\(a_1\) 最大),若 \(a_1\leq \sum^{k}_{i=2}a_i\),那麼這些葉子肯定可以與一個不在同一子樹內的葉子匹配,這樣所有點都覆蓋了它到 \(1\) 的路徑,肯定可以覆蓋完所有路徑。如果 \(a_1>\sum^{k}_{i=2}a_i\),那麼其他子樹都與這棵子樹中匹配,然後這個子樹成為了一個子問題。所以一定有解。
再觀察到,為了最小化路徑長度之和,肯定要讓儘量少的點連到 \(1\)

。如果 \(1\) 有偶數個兒子,那麼肯定所有兒子只會連上來一條邊;如果有奇數個兒子,那麼就只會存在恰好一個節點連上來兩條邊。所以對於每一條邊,最多被覆蓋兩次。
那麼只需要計算出最少多少條邊會被覆蓋兩次即可。如果一條邊被覆蓋兩次,當且僅當它子樹內恰好有偶數個葉子,因為如果這偶數個葉子兩兩匹配了,這一條邊就沒有被覆蓋了。
那麼維護每一個節點子樹內葉子數量即可。樹鏈剖分+線段樹就行了。
時間複雜度 \(O((\sum D)\log n)\)

程式碼

#include <bits/stdc++.h>
using namespace std;

const int N=100010;
int n,m,tot,cnt,a[N],deg[N],head[N],top[N],siz[N],son[N],id[N],fa[N];

struct edge
{
	int next,to;
}e[N*2];

void add(int from,int to)
{
	e[++tot]=(edge){head[from],to};
	head[from]=tot;
}

void dfs1(int x,int pa)
{
	fa[x]=pa; siz[x]=1;
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		if (v!=pa)
		{
			dfs1(v,x); siz[x]+=siz[v];
			if (siz[v]>siz[son[x]]) son[x]=v;
		} 
	}
}

void dfs2(int x,int tp)
{
	top[x]=tp; id[x]=++tot;
	if (son[x]) dfs2(son[x],tp);
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		if (v!=fa[x] && v!=son[x]) dfs2(v,v);
	}
}

struct SegTree
{
	int cnt[N*4],lazy[N*4];
	
	void pushdown(int x,int l,int r)
	{
		if (lazy[x])
		{
			int mid=(l+r)>>1;
			cnt[x*2]=mid-l+1-cnt[x*2];
			cnt[x*2+1]=r-mid-cnt[x*2+1];
			lazy[x*2]^=1; lazy[x*2+1]^=1; lazy[x]=0;
		}
	}
	
	void update(int x,int l,int r,int ql,int qr)
	{
		if (ql<=l && qr>=r)
		{
			cnt[x]=r-l+1-cnt[x]; lazy[x]^=1;
			return;
		}
		pushdown(x,l,r);
		int mid=(l+r)>>1;
		if (ql<=mid) update(x*2,l,mid,ql,qr);
		if (qr>mid) update(x*2+1,mid+1,r,ql,qr);
		cnt[x]=cnt[x*2]+cnt[x*2+1];
	}
}seg;

void update(int x)
{
	for (;x;x=fa[top[x]])
		seg.update(1,1,n,id[top[x]],id[x]);
}

int main()
{
	memset(head,-1,sizeof(head));
	scanf("%d%d",&n,&m);
	for (int i=1,x,y;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y); add(y,x);
		deg[x]++; deg[y]++;
	}
	tot=0;
	dfs1(1,0); dfs2(1,1);
	for (int i=1;i<=n;i++)
		if (deg[i]==1) update(i),cnt++;
	while (m--)
	{
		int k;
		scanf("%d",&k);
		for (int i=1;i<=k;i++)
		{
			scanf("%d",&a[i]);
			deg[a[i]]++;
			if (deg[a[i]]==2) continue;
			cnt++; update(a[i]);
		}
		if (cnt&1) cout<<"-1\n";
			else cout<<2*(n-1+k)-seg.cnt[1]-k<<"\n";
		for (int i=1;i<=k;i++)
		{
			deg[a[i]]--;
			if (deg[a[i]]==1) continue;
			cnt--; update(a[i]);
		}
	}
	return 0;
}