题解:P7201 [COCI 2019/2020 #1] Džumbus

题意

简化版题意:给定一个森林,所有节点初始为白色,可以花费 \(D_i\) 将第 \(i\) 个节点染成黑色。定义一个节点如果是黑色节点,且与它直接相连的点中有黑色节点,则这个点是“好的”。有 \(Q\) 个询问,每次询问在花费的总代价不超过 \(S_i\) 的情况下最多能将几个点变为"好的“点。

思路

首先建立一个超级源点(编号为 \(n+1\)),将每棵树的根与超级源点建边,这样森林就变成了一颗树。

考虑树形 DP。由于 \(n \leq 1000\),定义 \(f_{i,j,0}\) 为以 \(i\) 为根的子树中有 \(j\) 个好节点,且点 \(i\) 没有染成黑色的最小代价;\(f_{i,j,1}\) 为以 \(i\) 为根的子树中有 \(j\) 个好节点,且点 \(i\) 是好节点的最小代价。

考虑转移,对于一个点 \(i\) 及它的一个儿子 \(y\),有以下几种情况:

  • 什么都不做,则点 \(i\) 保持原来的状态,点 \(y\) 是不是好节点无所谓,所以有 \(f_{i,j,0} + \min(f_{y,k,0},f_{y,k,1}) \to f_{i,j+k,0}\) 和 \(f_{i,j,1} + \min(f_{y,k,0},f_{y,k,1}) \to f_{i,j+k,1}\)。

  • 将点 \(i\) 染黑,则有 \(f_{i,j,0} + D_i + f_{y,k,1} \to f_{i,j+k+1,1}\)。

  • 将点 \(y\) 染黑,则有 \(f_{i,j,1} + f_{y,k,0} + D_y \to f_{i,j+k+1,1}\)。

  • 将点 \(i\) 和点 \(y\) 同时染黑,则有 \(f_{i,j,0}+ D_i + f_{y,k,0} + D_y \to f_{i,j+k,1}\)。

最后记 \(f_{n+1,i,0}\) 为 \(ans_i\),由于 \(ans\) 没有单调性,即可能存在 \(i<j,ans_i>ans_j\),这种情况下 \(ans_i\) 是不会对答案有任何贡献的。所以建一个单调栈,每个元素入栈前将前面代价比它大的元素全部弹出,最后二分查找即可。

代码

#include<bits/stdc++.h>
#define MAXN 2005
#define int long long
#define INF INT_MAX
using namespace std;
int n,m,q,f[MAXN][MAXN][2],g[MAXN][2],cnt,head[MAXN],siz[MAXN],r[MAXN],top,d[MAXN];
vector<pair<int,int> >ans;
int find(int x){
	if(r[x]!=x)r[x]=find(r[x]);
	return r[x];
}
struct Edge{
	int value,next;
}edge[MAXN];
void addedge(int u,int v){
	edge[++cnt].value=v;
	edge[cnt].next=head[u];
	head[u]=cnt;
}
void upd(int &a,int b){
	if(a>b)a=b;
}
void dfs(int x,int fa){
	siz[x]=1;
	for(int i=head[x];i;i=edge[i].next){
		int y=edge[i].value;
		if(y!=fa){
			dfs(y,x);
			for(int j=0;j<=siz[x];j++){
				g[j][0]=f[x][j][0];
				g[j][1]=f[x][j][1];
			}
			for(int j=0;j<=siz[x];j++){
				for(int k=0;k<=siz[y];k++){
					upd(f[x][j+k][0],g[j][0]+f[y][k][1]);
					upd(f[x][j+k][0],g[j][0]+f[y][k][0]);
					
					upd(f[x][j+k][1],g[j][1]+f[y][k][1]);
					upd(f[x][j+k][1],g[j][1]+f[y][k][0]);
					
					upd(f[x][j+k+1][1],g[j][0]+d[x]+f[y][k][1]);
					upd(f[x][j+k+1][1],g[j][1]+f[y][k][0]+d[y]);
					upd(f[x][j+k+2][1],g[j][0]+d[x]+f[y][k][0]+d[y]);
				}
			}
			siz[x]+=siz[y];
		}
	}
}
signed main(){
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++)r[i]=i;
	for(int i=1;i<=n;i++)scanf("%lld",&d[i]);
	d[n+1]=INF;  //防止超级源点被染色 
	for(int i=1;i<=n+1;i++){
		for(int j=1;j<=n+1;j++){
			f[i][j][0]=f[i][j][1]=INF;
		}
		f[i][0][1]=INF; //初始f[i][0][0]=0 
	}
	for(int i=1;i<=m;i++){
		int u,v;scanf("%lld%lld",&u,&v);
		addedge(u,v),addedge(v,u);
		if(find(u)!=find(v))r[find(u)]=find(v);
	}
	for(int i=1;i<=n;i++){
		if(find(i)==i)addedge(i,n+1),addedge(n+1,i);//每颗树根与超级源点建边 
	}
	dfs(n+1,-1);
	for(int i=0;i<=n;i++){//维护单调栈 
		while(top&&ans[top-1].first>=f[n+1][i][0]){
			ans.pop_back(),top--;
		}
		ans.push_back(make_pair(f[n+1][i][0],i)),top++;
	}
	scanf("%lld",&q);
	for(int i=1;i<=q;i++){
		int x;scanf("%lld",&x);
		int l=1,r=top,mid,tans=top;
		while(l<=r){//二分查找 
			mid=(l+r)>>1;
			if(ans[mid-1].first<=x){
				tans=mid;
				l=mid+1;
			}else{
				r=mid-1;
			}
		}
		printf("%lld\n",ans[tans-1].second);
	}
	return 0;
}

posted @ 2025-11-02 22:15  CharlieCai2024  阅读(25)  评论(0)    收藏  举报