P2538 [SCOI2008] 城堡
Solution
可以先做下 这题。
先考虑不包含环的情况,可以二分答案,题目就转换为对于给定的 \(K\),已知选择了 \(M\) 个点,再选择尽可能少的节点,使得所有点都被「覆盖」。
「覆盖」的定义为:存在一个被选择的点与这个节点的距离不大于 \(K\)。
考虑贪心 + dp,直到不得不选择时才选择。设 \(f_i\) 表示 \(i\) 子树内距离 \(i\) 最远没被覆盖的点,\(g_i\) 表示 \(i\) 子树内距离 \(i\) 最近被选择的点。
那么有:
\(f_u=\max\limits_{v \in son_u}(f_v+dis_{v,u})\)
\(g_u=\min\limits_{v \in son_u}(g_v+dis_{v,u})\)
接下来就是分类讨论,设二分的值为 \(mid\):
- \(u\) 已经被选过了,有 \(f_u=-inf,g_u=0\)。
- 若 \(f_u+g_u\le mid\),说明离 \(u\) 最远没被覆盖的点可以被覆盖到,\(f_u=-inf\)。
- 若 \(g_u > mid\),说明 \(u\) 无法被 \(u\) 子树内的点覆盖,那么 \(f_u=\max(f_u,0)\)。即若 \(f_u > 0\),说明有子树有比它更深的未被覆盖的点,只需考虑比它深的点。若 \(f_u<0\) 说明 \(f_u\) 就是最深未被覆盖的点,\(f_u=0\)。
- 若 \(f_u+dis_{u,fa_u} > mid\),则如果 \(u\) 不被选择,那么最深的那个点则再也无法被覆盖,\(f_u=-inf,g_u=0\),选的点个数加 \(1\)。
但是它是一颗基环树,则可以考虑删除基环上的一条边,将其转换成一棵树,然后按上述过程处理即可即可。
注意:存在基环树森林,要将每棵树分开处理。
Code
#include<bits/stdc++.h>
#define IOS cin.tie(0),cout.tie(0),ios::sync_with_stdio(0)
#define ll long long
#define db double
#define pb push_back
#define eb emplace_back
#define MS(x,y) memset(x,y,sizeof x)
#define MC(x,y) memcpy(x,y,sizeof x)
#define PLL pair<ll,ll>
#define lb(x) (x&-x)
using namespace std;
const int N=50+5,M=1e5+5;
const ll INF=1ll<<60,mod=998244353;
int n,m,k,Tot,r[N],deg[N];
ll f[N],g[N],df[N],d[N];//df u 表示到 u 父亲的距离
vector<int> tr[N];//基环树森林,tr[i] 表示以 i 为根时子树的所有节点
bool cir[N],is[N],vis[N],ff[N];
//cir 表示是否在环上,is 表示是否已经被选择,ff 表示是否为一颗基环树的根
struct node{
ll v,dis,id;//表示到的节点,距离,边的编号
};
vector<node> to[N];
void init(int u,int tf){
vis[u]=1;tr[tf].pb(u);
for(auto tp:to[u]) if(!vis[tp.v]) init(tp.v,tf);
}
void dfs(int u,int fa,int del,int mid){
f[u]=-INF,g[u]=INF;
for(auto tp:to[u]){
int id=tp.id,v=tp.v,dis=tp.dis;
if(v==fa || del==id) continue;
df[v]=dis;
dfs(v,u,del,mid);
f[u]=max(f[v]+dis,f[u]);
g[u]=min(g[v]+dis,g[u]);
}
if(is[u]) f[u]=-INF,g[u]=0;
if(f[u]+g[u]<=mid) f[u]=-INF;
if(g[u]>mid) f[u]=max(f[u],0ll);
if(f[u]+df[u]>mid) f[u]=-INF,g[u]=0,Tot++;
}
int calc(int tf,int mid){
if(!ff[tf]) return 0;//要满足 tf 为这颗基环树的根
Tot=0;
int Cnt=mod;
for(int i:tr[tf]){//枚举删的边
if(!cir[i]) continue;
Tot=0;
dfs(tf,0,i,mid);
if(f[tf]>=0) Tot++;//根内存在未被覆盖的
Cnt=min(Cnt,Tot);
}
return Cnt;
}
bool check(int mid){
int tot=0;
for(int i=1;i<=n;i++) tot+=calc(i,mid);
return tot<=k;
}
void topo(){//拓扑求环,转换为 i->r[i] 的有向图,这个点如果在有向图的环上则在基环上
queue<int> q;
for(int i=1;i<=n;i++) if(!deg[i]) q.push(i);
while(!q.empty()){
int u=q.front();q.pop();
int v=r[u];
if(--deg[v]==0) q.push(v);
}
for(int i=1;i<=n;i++) cir[i]=deg[i];
}
int main(){
IOS;cin>>n>>m>>k;
//转化为下标为 1 ~ n
for(int i=1;i<=n;i++) cin>>r[i],r[i]++;
for(int i=1;i<=n;i++) cin>>d[i];
for(int i=1;i<=n;i++){
to[i].pb({r[i],d[i],i}),to[r[i]].pb({i,d[i],i});//无向图
deg[r[i]]++;
}
for(int i=1,x;i<=m;i++) cin>>x,is[++x]=1;
topo();
for(int i=1;i<=n;i++){
if(!vis[i]) ff[i]=1,init(i,i);//遍历一整棵树,这棵基环树以 i 为根
}
int l=0,r=5e7;//二分答案
while(l<r){
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l<<"\n";
return 0;
}

浙公网安备 33010602011771号