【洛谷P6805】春季大掃除
阿新 • • 發佈:2021-10-22
題目
題目連結:https://www.luogu.com.cn/problem/P6805
春季大掃除也許是我們一生中最無聊的事情之一。當然,對於 Flóra 和她的母親而言,今年的春季大掃除要有意思得多。因為她們在地毯下發現了一張已被灰塵覆蓋的樹形地圖。
這棵樹有 \(N\) 個節點,節點從 \(1\) 到 \(N\) 進行編號,這 \(N\) 個點通過 \(N-1\) 條邊相連。這些邊上都積累了過多的灰塵,因此 Flóra 的母親準備對這棵樹進行清理。
清理這棵樹的過程是這樣的:Flóra 的母親每次在這棵樹上選擇兩個葉子節點(定義一棵樹的葉子節點為只與恰好一個點直接相連的點),並對這兩個葉子點路徑上的所有邊進行清理。如果這條路徑上有 \(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\)
那麼只需要計算出最少多少條邊會被覆蓋兩次即可。如果一條邊被覆蓋兩次,當且僅當它子樹內恰好有偶數個葉子,因為如果這偶數個葉子兩兩匹配了,這一條邊就沒有被覆蓋了。
那麼維護每一個節點子樹內葉子數量即可。樹鏈剖分+線段樹就行了。
時間複雜度 \(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;
}