「SDOI2011」消耗战 题解

Statement

SDOI2011消耗战 - 洛谷

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;
} 
posted @ 2021-08-27 10:15  _Famiglistimo  阅读(39)  评论(0编辑  收藏  举报