题解:P17320 [ICPC 2018 Nanjing R] Cherry and Chocolate
lailai0916 · · 题解
题意简述
在树上依次选择粉色点、棕色点、粉色点。路径经过粉色点的节点会得分。Cherry 最大化得分,Chocolate 最小化得分,求最终得分。
解题思路
删除两个粉色点后,一个节点得分当且仅当它不在棕色点所在的连通块中。因此,若这个连通块大小为
固定第一个粉色点
若
以
记删除
于是,对删除一条边后得到的每个连通块,只需知道其大小及重心处的最大分支大小。
将每条无向边拆成两条有向边。对有向边
令
则
预处理每个节点最大、次大的出边,便能在排除反向边后求出
停止时,所有向前分支大小均不超过
该重心朝起始边方向的分支大小为
若
第一个粉色点为
倍增预处理和逐边查询的时间复杂度均为
参考代码
#include <bits/stdc++.h>
using namespace std;
const int N=100005;
const int M=200005;
const int K=18;
vector<int> g[N];
int to[M],siz[M],val[M];
int fa[N],ped[N],sub[N],fst[N],sec[N];
int nxt[K][M];
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin>>n;
int m=2*n-2;
for(int i=0;i<m;i+=2)
{
int u,v;
cin>>u>>v;
to[i]=v;
to[i^1]=u;
g[u].push_back(i);
g[v].push_back(i^1);
}
vector<int> ord(1,1);
for(int i=0;i<n;i++)
{
int u=ord[i];
for(int e:g[u])
{
int v=to[e];
if(v==fa[u])continue;
fa[v]=u;
ped[v]=e;
ord.push_back(v);
}
}
for(int i=1;i<=n;i++)sub[i]=1;
for(int i=n-1;i>0;i--)
{
int u=ord[i];
sub[fa[u]]+=sub[u];
siz[ped[u]]=sub[u];
siz[ped[u]^1]=n-sub[u];
}
for(int i=1;i<=n;i++)
{
fst[i]=sec[i]=-1;
for(int e:g[i])
{
if(fst[i]==-1||siz[e]>siz[fst[i]])
{
sec[i]=fst[i];
fst[i]=e;
}
else if(sec[i]==-1||siz[e]>siz[sec[i]])sec[i]=e;
}
}
for(int i=0;i<m;i++)
{
int v=to[i];
nxt[0][i]=fst[v]!=(i^1)?fst[v]:sec[v];
}
for(int i=1;i<K;i++)
{
for(int j=0;j<m;j++)
{
int ne=nxt[i-1][j];
nxt[i][j]=ne==-1?-1:nxt[i-1][ne];
}
}
for(int i=0;i<m;i++)
{
int f=i;
for(int k=K-1;k>=0;k--)
{
int ne=nxt[k][f];
if(ne!=-1&&siz[ne]*2>siz[i])f=ne;
}
int mx=siz[i]-siz[f];
if(nxt[0][f]!=-1)mx=max(mx,siz[nxt[0][f]]);
val[i]=siz[i]-mx;
}
int ans=n;
for(int i=1;i<=n;i++)
{
int mx=0;
for(int e:g[i])mx=max(mx,val[e]);
ans=min(ans,mx);
}
cout<<n-ans<<'\n';
return 0;
}