虚树
虚树
模板
点击查看代码
struct Tree{ //原树
int tot,head[maxn],nx[maxn],to[maxn];
ll w[maxn];
void add(int x,int y,ll z){
to[++tot]=y;nx[tot]=head[x];head[x]=tot;
w[tot]=z;
}
int cnt,dfn[maxn],f[maxn][21],dep[maxn];
void dfs(int x,int fa){
dfn[x]=++cnt;dep[x]=dep[fa]+1;
for(int i=1;i<=20;i++)f[x][i]=f[f[x][i-1]][i-1];
for(int i=head[x];i;i=nx[i]){
int v=to[i];if(v==fa)continue;
f[v][0]=x;
dfs(v,x);
}
return ;
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=20;i>=0;i--){
if(dep[f[x][i]]>=dep[y])x=f[x][i];
}
if(x==y)return x;
for(int i=20;i>=0;i--){
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
}T;
struct Vtree{
int tot,head[maxn],nx[maxn],to[maxn];
// ll w[maxn]; 根据题目需要
void add(int x,int y){
to[++tot]=y;nx[tot]=head[x];head[x]=tot;
to[++tot]=x;nx[tot]=head[y];head[y]=tot;
// w[tot]=z;
}
int h[maxn],k; //储存关键点
int st[maxn],top; //用单调栈来建虚树
/*
其他信息
*/
void build(){
sort(h+1,h+k+1,[](int i,int j){
return T.dfn[i]<T.dfn[j];
});
st[top=1]=1;
for(int i=1;i<=k;i++){
s.insert(h[i]);
if(h[i]!=1){
int l=T.lca(h[i],st[top]);
if(l!=st[top]){
while(T.dfn[l]<T.dfn[st[top-1]]){
add(st[top],st[top-1]);
top--;
}
if(T.dfn[l]>T.dfn[st[top-1]]){
add(l,st[top]);
st[top]=l;
}
else add(l,st[top--]);
}
st[++top]=h[i];
}
}
for(int i=1;i<top;i++)add(st[i],st[i+1]);
return ;
}
//以下根据题目修改
void init(){
tot=0;
/*
*/
}
void solve(){
/*
*/
build();
/*
*/
init();
}
}V;
例题
1. [SDOI2011] 消耗战
首先考虑普通dp
设\(dp_u\)表示以u为根的子树中,割掉所有给定点的最小代价
1.若u不是给定点
\(dp_u=min(minn[u],\sum_{v\in S}dp_v)\)
\(minn[u]\)为根到\(u\)路径边权值的最小值
\(S\)为\(u\)的相邻点的集合
2.若u是给定点
\(dp_u=minn[u]\)
复杂度\(O(nm)\),这样肯定会超时
构建虚树,在虚树上dp即可
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=5e5+101;
const int MOD=1e9+7;
const ll inf=9223372036854775807;
const double eps=1e-12;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
struct Tree{
int tot,head[maxn],nx[maxn],to[maxn];
ll w[maxn];
void add(int x,int y,ll z){
to[++tot]=y;nx[tot]=head[x];head[x]=tot;
w[tot]=z;
}
int cnt,dfn[maxn],f[maxn][21],dep[maxn];
ll minn[maxn];
void dfs(int x,int fa){
dfn[x]=++cnt;dep[x]=dep[fa]+1;
for(int i=1;i<=20;i++){
f[x][i]=f[f[x][i-1]][i-1];
}
for(int i=head[x];i;i=nx[i]){
int v=to[i];if(v==fa)continue;
f[v][0]=x;minn[v]=min(minn[x],w[i]);
dfs(v,x);
}
return ;
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=20;i>=0;i--){
if(dep[f[x][i]]>=dep[y])x=f[x][i];
}
if(x==y)return x;
for(int i=20;i>=0;i--){
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
}T;
struct Vtree{
int tot,head[maxn],nx[maxn],to[maxn];
ll w[maxn];
void add(int x,int y){
to[++tot]=y;nx[tot]=head[x];head[x]=tot;
to[++tot]=x;nx[tot]=head[y];head[y]=tot;
// w[tot]=z;
}
int h[maxn],k; //储存关键点
int st[maxn],top;
set<int>s; //关键点的集合
void build(){
sort(h+1,h+k+1,[](int i,int j){
return T.dfn[i]<T.dfn[j];
});
st[top=1]=1;
for(int i=1;i<=k;i++){
s.insert(h[i]);
if(h[i]!=1){
int l=T.lca(h[i],st[top]);
if(l!=st[top]){
while(T.dfn[l]<T.dfn[st[top-1]]){
add(st[top],st[top-1]);
top--;
}
if(T.dfn[l]>T.dfn[st[top-1]]){
add(l,st[top]);
st[top]=l;
}
else{
add(l,st[top--]);
}
}
st[++top]=h[i];
}
}
for(int i=1;i<top;i++){
add(st[i],st[i+1]);
}
return ;
}
void init(){
tot=0;
s.clear();
}
ll dp(int u,int fa){
ll ans=inf;
for(int i=head[u];i;i=nx[i]){
int v=to[i];if(v==fa)continue;
if(ans==inf)ans=dp(v,u);
else ans+=dp(v,u);
}
head[u]=0;
if(s.count(u))return T.minn[u];
else return min(T.minn[u],ans);
}
void solve(){
k=read();
for(int i=1;i<=k;i++)h[i]=read();
build();
printf("%lld\n",dp(1,0));
init();
}
}V;
int n,m;
int main(){
n=read();
for(int i=1;i<n;i++){
int x=read(),y=read(),z=read();
T.add(x,y,z);T.add(y,x,z);
}
T.minn[1]=inf;T.dfs(1,0);
m=read();
while(m--)V.solve();
return 0;
}
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=2e6+101;
const int MOD=1e9+7;
const ll inf=2147483647;
const double eps=1e-12;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
struct Tree{
int tot,head[maxn],nx[maxn],to[maxn];
void add(int x,int y){
to[++tot]=y;nx[tot]=head[x];head[x]=tot;
}
int cnt,dfn[maxn],f[maxn][21],dep[maxn];
void dfs(int x,int fa){
dfn[x]=++cnt;dep[x]=dep[fa]+1;
for(int i=1;i<=20;i++)f[x][i]=f[f[x][i-1]][i-1];
for(int i=head[x];i;i=nx[i]){
int v=to[i];if(v==fa)continue;
f[v][0]=x;
dfs(v,x);
}
return ;
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=20;i>=0;i--){
if(dep[f[x][i]]>=dep[y])x=f[x][i];
}
if(x==y)return x;
for(int i=20;i>=0;i--){
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
int getdis(int x,int y){
return dep[x]+dep[y]-2*dep[lca(x,y)];
}
}T;
struct Vtree{
int tot,head[maxn],nx[maxn],to[maxn];
void add(int x,int y){
to[++tot]=y;nx[tot]=head[x];head[x]=tot;
to[++tot]=x;nx[tot]=head[y];head[y]=tot;
}
int h[maxn],k; //储存关键点
int st[maxn],top;
set<int>s; //关键点的集合
void build(){
sort(h+1,h+k+1,[](int i,int j){
return T.dfn[i]<T.dfn[j];
});
st[top=1]=1;
for(int i=1;i<=k;i++){
s.insert(h[i]);
if(h[i]!=1){
int l=T.lca(h[i],st[top]);
if(l!=st[top]){
while(T.dfn[l]<T.dfn[st[top-1]]){
add(st[top],st[top-1]);
top--;
}
if(T.dfn[l]>T.dfn[st[top-1]]){
add(l,st[top]);
st[top]=l;
}
else add(l,st[top--]);
}
st[++top]=h[i];
}
}
for(int i=1;i<top;i++)add(st[i],st[i+1]);
return ;
}
ll al;
int deep[maxn],low[maxn];
void init(){
tot=0;al=0;
s.clear();
}
ll sz[maxn];
void dfs(int u,int fa){
sz[u]=0;
if(s.count(u))sz[u]=1;
for(int i=head[u];i;i=nx[i]){
int v=to[i];if(v==fa)continue;
dfs(v,u);sz[u]+=sz[v];
}
return ;
}
pa dp(int u,int fa){
int maxx=-1,minn=inf;//最大值,最小值
int getde=-1,getlow=inf; //最大深度,最小深度
if(s.count(u))getde=getlow=0;
deep[u]=0,low[u]=0;
for(int i=head[u];i;i=nx[i]){
int v=to[i];if(v==fa)continue;
pa now=dp(v,u);
maxx=max(now.fi,maxx);
minn=min(now.se,minn);
ll w=T.getdis(u,v);
deep[v]+=w;low[v]+=w;
if(getde==-1){
getde=getlow=deep[v];
if(s.count(u))maxx=minn=deep[v];
}
else {
maxx=max(maxx,getde+deep[v]);
minn=min(minn,getlow+low[v]);
}
getde=max(getde,deep[v]);
getlow=min(getlow,low[v]);
al+=w*(k-sz[v])*sz[v];
}
deep[u]=getde;low[u]=getlow;
head[u]=0;
return mp(maxx,minn);
}
void solve(){
k=read();
for(int i=1;i<=k;i++)h[i]=read();
build();
dfs(1,0);
pa ans=dp(1,0);
printf("%lld %d %d\n",al,ans.se,ans.fi);
init();
}
}V;
int n,m;
int main(){
n=read();
for(int i=1;i<n;i++){
int x=read(),y=read();
T.add(x,y);T.add(y,x);
}
T.dfs(1,0);
m=read();
while(m--)V.solve();
return 0;
}
3.D. Kingdom and its Cities
题意:n个点的树,q次询问,每次询问给出k个关键点的集合,问是否能删去一些非关键点,使得所有关键点不连通,若可行输出最小删点个数
题解:
无解情况:若存在一个点和它的父亲都是关键点,那么输出-1
有解:
不难发现,删去的点都属于k个关键点两两lca的集合,并且总共k很小
建立虚树,选择虚树上的贪心
若虚树上一个非关键点有多个分支有多个关键点,那么这个点删去,并且向上传递当前子树0个关键点
因为当前非关键点的子树已经保证不连通了
若虚树上一个关键点连着的一个或者多个分支有关键点,那么要删去有关键点的分支个数
因为建立虚树时,若两个在原树相邻关键点,在虚树上直接相连,需要单独考虑
点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long
#define pa pair<int,int>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define YES {puts("YES");return;}
#define NO {puts("NO");return ;}
using namespace std;
const int maxn=2e6+101;
const int MOD=1e9+7;
const ll inf=2147483647;
const double eps=1e-12;
ll read(){
ll x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
return x*f;
}
struct Tree{
int tot,head[maxn],nx[maxn],to[maxn];
void add(int x,int y){
to[++tot]=y;nx[tot]=head[x];head[x]=tot;
}
int cnt,dfn[maxn],f[maxn][21],dep[maxn];
void dfs(int x,int fa){
dfn[x]=++cnt;dep[x]=dep[fa]+1;
for(int i=1;i<=20;i++)f[x][i]=f[f[x][i-1]][i-1];
for(int i=head[x];i;i=nx[i]){
int v=to[i];if(v==fa)continue;
f[v][0]=x;
dfs(v,x);
}
return ;
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=20;i>=0;i--){
if(dep[f[x][i]]>=dep[y])x=f[x][i];
}
if(x==y)return x;
for(int i=20;i>=0;i--){
if(f[x][i]!=f[y][i]){
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
int getdis(int x,int y){
return dep[x]+dep[y]-2*dep[lca(x,y)];
}
}T;
struct Vtree{
int tot,head[maxn],nx[maxn],to[maxn];
void add(int x,int y){
to[++tot]=y;nx[tot]=head[x];head[x]=tot;
to[++tot]=x;nx[tot]=head[y];head[y]=tot;
}
int h[maxn],k; //储存关键点
int st[maxn],top;
set<int>s; //关键点的集合
void build(){
sort(h+1,h+k+1,[](int i,int j){
return T.dfn[i]<T.dfn[j];
});
st[top=1]=1;
for(int i=1;i<=k;i++){
if(h[i]!=1){
int l=T.lca(h[i],st[top]);
if(l!=st[top]){
while(T.dfn[l]<T.dfn[st[top-1]]){
add(st[top],st[top-1]);
top--;
}
if(T.dfn[l]>T.dfn[st[top-1]]){
add(l,st[top]);
st[top]=l;
}
else add(l,st[top--]);
}
st[++top]=h[i];
}
}
for(int i=1;i<top;i++)add(st[i],st[i+1]);
return ;
}
void init(){
tot=0;ans=0;
s.clear();
}
int ans;
int dfs(int u,int fa){
int cnt=0;
for(int i=head[u];i;i=nx[i]){
int v=to[i];if(v==fa)continue;
cnt+=dfs(v,u);
}
head[u]=0;
if(!s.count(u)){
if(cnt>1)ans++,cnt=0;
return cnt;
}
else {
ans+=cnt;
return 1;
}
}
void solve(){
k=read();init();
for(int i=1;i<=k;i++){
h[i]=read();
s.insert(h[i]);
}
for(int i=1;i<=k;i++){
if(s.count(T.f[h[i]][0])){puts("-1");return ;}
}
build();
dfs(1,0);
printf("%d\n",ans);
return ;
}
}V;
int n,m;
int main(){
n=read();
for(int i=1;i<n;i++){
int x=read(),y=read();
T.add(x,y);T.add(y,x);
}
T.dfs(1,0);
m=read();
while(m--)V.solve();
return 0;
}