「SDOI2011」消耗战 题解
Statement
Solve1
虚树上DP
简化题意:
给定一棵 𝑛 个点的树(边带权)以及若干组关键点,对每一组求删边的最少代价(删边的代价为边权)可以使关键点与 1 号节点不连通。
\(n\leq 2.5\times 10^5,\sum k\leq 5\times 10^5\)
看到 \(\sum k\) ,我们可以想到虚树
先建虚树。
预处理数组 \(minn[u]\) 表示从 1 到 \(u\) 路径上边权最小值;
设数组 \(dp[u]\) 表示令 \(u\) 为根的子树满 足条件的最小代价。
- 若 \(u\) 是关键点,则 \(dp[u]=minn[u]\)
- 若 \(u\) 不是关键点,则 \(dp[u]=\min(minn[u],\sum dp[v])\) 。
最终答案即 \(dp[1]\)。
Code1
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define mp make_pair
#define fi first
#define se second
using namespace std;
const int N = 3e5+5;
struct Edge{
int nex,to;
}edge[N<<1];
vector<pii>Edge[N];
int head[N],dfn[N],dep[N],top[N],f[N];
int son[N],siz[N],a[N],fg[N];
int n,m,k,tim,elen;
int minn[N];
void dfs1(int u){
siz[u]=1,dfn[u]=++tim;
for(pii e:Edge[u])
if(e.fi^f[u]){
dep[e.fi]=dep[f[e.fi]=u]+1,
minn[e.fi]=min(minn[u],e.se),
dfs1(e.fi),siz[u]+=siz[e.fi];
if(siz[e.fi]>siz[son[u]])son[u]=e.fi;
}
}
void dfs2(int u,int tp){
top[u]=tp;
if(son[u])dfs2(son[u],tp);
for(pii e:Edge[u])
if(e.fi!=f[u]&&e.fi!=son[u])
dfs2(e.fi,e.fi);
}
int lca(int x,int y){
while(top[x]!=top[y])
dep[top[x]]<=dep[top[y]]?y=f[top[y]]:x=f[top[x]];
return dep[x]<dep[y]?x:y;
}
bool cmp(int x,int y){return dfn[x]<dfn[y];}
void add(int u,int v){edge[++elen]={head[u],v},head[u]=elen;}
int DP(int u){
int sum=0,res;
for(int e=head[u];e;e=edge[e].nex)
sum+=DP(edge[e].to);
res=fg[u]?minn[u]:min(minn[u],sum);
return fg[u]=head[u]=0,res;
}
signed main(){
scanf("%lld",&n);
for(int i=1,u,v,w;i<n;++i)
scanf("%lld%lld%lld",&u,&v,&w),
Edge[u].push_back(mp(v,w)),
Edge[v].push_back(mp(u,w));
minn[1]=1e18;
dfs1(1),dfs2(1,1);
scanf("%lld",&m);
while(m--){
elen=0;
scanf("%lld",&k);
for(int i=1;i<=k;++i)
scanf("%lld",&a[i]),fg[a[i]]=1;
int cnt=k;
sort(a+1,a+k+1,cmp);
for(int i=2;i<=k;++i){
int l=lca(a[i-1],a[i]);
if(a[i-1]!=l&&a[i]!=l)a[++cnt]=l;
}
sort(a+1,a+cnt+1);
cnt=unique(a+1,a+cnt+1)-a-1;
sort(a+1,a+cnt+1,cmp);
for(int i=2;i<=cnt;++i)
add(lca(a[i],a[i-1]),a[i]);
printf("%lld\n",DP(a[1]));
}
return 0;
}
Solve2
线段树合并
我们知道直接 \(dp\) 是 \(O(nq)\) 的
虚树优化了 \(n\) ,使得复杂度变成了 \(O(q\ k\log k)\)
为什么不能对 \(q\) 进行优化呢?我们考虑把询问离线。
我们以 \(q\) 为下标,对于所有的关键点建立线段树
让 \(tree[root[u]]\) 所代表的线段树表示截断 \(u\) 和子树内关键点的联系的最小代价
那么,对于一个询问 \(q\) ,我们从叶子到根,线段树合并起来就是答案
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e5+5;
const ll inf = 1e18;
struct Edge{
int nex,to;ll dis;
}edge[N],qus[N];
struct Tree{
int ls,rs;ll v;
}t[N<<5];
int ehead[N],qhead[N],rot[N];
int n,m,k,x,elen,qlen,siz;
bool cmin(ll &x,ll y){return x>y?x=y,1:0;}
void addedge(int u,int v,int w){
edge[++elen]={ehead[u],v,w},ehead[u]=elen;
edge[++elen]={ehead[v],u,w},ehead[v]=elen;
}
void addqus(int u,int v,int w=0){
qus[++qlen]={qhead[u],v,w};
qhead[u]=qlen;
}
void insert(int l,int r,int& rt,int id){
if(id<l||id>r)return;
if(!rt)t[rt=++siz].v=inf;
if(l==r)return;int mid=(l+r)>>1;
insert(l,mid,t[rt].ls,id);
insert(mid+1,r,t[rt].rs,id);
}
void pushdown(int rt){
cmin(t[t[rt].ls].v,t[rt].v);
cmin(t[t[rt].rs].v,t[rt].v);
t[rt].v=inf;
}
int merge(int x,int y){
if(!x||!y)return x+y;
if(!t[x].ls&&!t[x].rs)return t[x].v+=t[y].v,x;
pushdown(x),pushdown(y);
t[x].ls=merge(t[x].ls,t[y].ls);
t[x].rs=merge(t[x].rs,t[y].rs);
return x;
}
void dfs(int u,int fath){
for(int e=qhead[u],v;v=qus[e].to,e;e=qus[e].nex)
insert(1,m,rot[u],v);
for(int e=ehead[u],v;v=edge[e].to,e;e=edge[e].nex)
if(v^fath)dfs(v,u),cmin(t[rot[v]].v,edge[e].dis),rot[u]=merge(rot[u],rot[v]);
}
void print(int l,int r,int rt){
if(l==r)return printf("%lld\n",t[rt].v),void();
pushdown(rt);int mid=(l+r)>>1;
print(l,mid,t[rt].ls),print(mid+1,r,t[rt].rs);
}
int main(){
scanf("%d",&n);
for(int i=1,u,v,w;i<n;++i)
scanf("%d%d%d",&u,&v,&w),addedge(u,v,w);
scanf("%d",&m);
for(int i=1;i<=m;++i)
for(scanf("%d",&k);k--;)
scanf("%d",&x),addqus(x,i);
dfs(1,0);
print(1,m,rot[1]);
return 0;
}