题解: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;
}
浙公网安备 33010602011771号